Merge branch 'master' into next
[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 import logging
27
28 from ganeti import luxi
29 from ganeti import rapi
30 from ganeti import http
31 from ganeti import ssconf
32 from ganeti import constants
33 from ganeti import opcodes
34 from ganeti import errors
35
36
37 def BuildUriList(ids, uri_format, uri_fields=("name", "uri")):
38   """Builds a URI list as used by index resources.
39
40   @param ids: list of ids as strings
41   @param uri_format: format to be applied for URI
42   @param uri_fields: optional parameter for field IDs
43
44   """
45   (field_id, field_uri) = uri_fields
46
47   def _MapId(m_id):
48     return { field_id: m_id, field_uri: uri_format % m_id, }
49
50   # Make sure the result is sorted, makes it nicer to look at and simplifies
51   # unittests.
52   ids.sort()
53
54   return map(_MapId, ids)
55
56
57 def ExtractField(sequence, index):
58   """Creates a list containing one column out of a list of lists.
59
60   @param sequence: sequence of lists
61   @param index: index of field
62
63   """
64   return map(lambda item: item[index], sequence)
65
66
67 def MapFields(names, data):
68   """Maps two lists into one dictionary.
69
70   Example::
71       >>> MapFields(["a", "b"], ["foo", 123])
72       {'a': 'foo', 'b': 123}
73
74   @param names: field names (list of strings)
75   @param data: field data (list)
76
77   """
78   if len(names) != len(data):
79     raise AttributeError("Names and data must have the same length")
80   return dict(zip(names, data))
81
82
83 def _Tags_GET(kind, name=""):
84   """Helper function to retrieve tags.
85
86   """
87   if kind == constants.TAG_INSTANCE or kind == constants.TAG_NODE:
88     if not name:
89       raise http.HttpBadRequest("Missing name on tag request")
90     cl = GetClient()
91     if kind == constants.TAG_INSTANCE:
92       fn = cl.QueryInstances
93     else:
94       fn = cl.QueryNodes
95     result = fn(names=[name], fields=["tags"], use_locking=False)
96     if not result or not result[0]:
97       raise http.HttpBadGateway("Invalid response from tag query")
98     tags = result[0][0]
99   elif kind == constants.TAG_CLUSTER:
100     ssc = ssconf.SimpleStore()
101     tags = ssc.GetClusterTags()
102
103   return list(tags)
104
105
106 def _Tags_PUT(kind, tags, name=""):
107   """Helper function to set tags.
108
109   """
110   return SubmitJob([opcodes.OpAddTags(kind=kind, name=name, tags=tags)])
111
112
113 def _Tags_DELETE(kind, tags, name=""):
114   """Helper function to delete tags.
115
116   """
117   return SubmitJob([opcodes.OpDelTags(kind=kind, name=name, tags=tags)])
118
119
120 def MapBulkFields(itemslist, fields):
121   """Map value to field name in to one dictionary.
122
123   @param itemslist: a list of items values
124   @param fields: a list of items names
125
126   @return: a list of mapped dictionaries
127
128   """
129   items_details = []
130   for item in itemslist:
131     mapped = MapFields(fields, item)
132     items_details.append(mapped)
133   return items_details
134
135
136 def MakeParamsDict(opts, params):
137   """Makes params dictionary out of a option set.
138
139   This function returns a dictionary needed for hv or be parameters. But only
140   those fields which provided in the option set. Takes parameters frozensets
141   from constants.
142
143   @type opts: dict
144   @param opts: selected options
145   @type params: frozenset
146   @param params: subset of options
147   @rtype: dict
148   @return: dictionary of options, filtered by given subset.
149
150   """
151   result = {}
152
153   for p in params:
154     try:
155       value = opts[p]
156     except KeyError:
157       continue
158     result[p] = value
159
160   return result
161
162
163 def SubmitJob(op, cl=None):
164   """Generic wrapper for submit job, for better http compatibility.
165
166   @type op: list
167   @param op: the list of opcodes for the job
168   @type cl: None or luxi.Client
169   @param cl: optional luxi client to use
170   @rtype: string
171   @return: the job ID
172
173   """
174   try:
175     if cl is None:
176       cl = GetClient()
177     return cl.SubmitJob(op)
178   except errors.JobQueueFull:
179     raise http.HttpServiceUnavailable("Job queue is full, needs archiving")
180   except errors.JobQueueDrainError:
181     raise http.HttpServiceUnavailable("Job queue is drained, cannot submit")
182   except luxi.NoMasterError, err:
183     raise http.HttpBadGateway("Master seems to unreachable: %s" % str(err))
184   except luxi.TimeoutError, err:
185     raise http.HttpGatewayTimeout("Timeout while talking to the master"
186                                   " daemon. Error: %s" % str(err))
187
188 def GetClient():
189   """Geric wrapper for luxi.Client(), for better http compatiblity.
190
191   """
192   try:
193     return luxi.Client()
194   except luxi.NoMasterError, err:
195     raise http.HttpBadGateway("Master seems to unreachable: %s" % str(err))
196
197
198 def FeedbackFn(ts, log_type, log_msg):
199   """Feedback logging function for http case.
200
201   We don't have a stdout for printing log messages, so log them to the
202   http log at least.
203
204   """
205   logging.info("%s: %s", log_type, log_msg)
206
207
208 class R_Generic(object):
209   """Generic class for resources.
210
211   """
212   # Default permission requirements
213   GET_ACCESS = []
214   PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE]
215   POST_ACCESS = [rapi.RAPI_ACCESS_WRITE]
216   DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE]
217
218   def __init__(self, items, queryargs, req):
219     """Generic resource constructor.
220
221     @param items: a list with variables encoded in the URL
222     @param queryargs: a dictionary with additional options from URL
223
224     """
225     self.items = items
226     self.queryargs = queryargs
227     self.req = req
228     self.sn = None
229
230   def getSerialNumber(self):
231     """Get Serial Number.
232
233     """
234     return self.sn
235
236   def _checkIntVariable(self, name):
237     """Return the parsed value of an int argument.
238
239     """
240     val = self.queryargs.get(name, 0)
241     if isinstance(val, list):
242       if val:
243         val = val[0]
244       else:
245         val = 0
246     try:
247       val = int(val)
248     except (ValueError, TypeError):
249       raise http.HttpBadRequest("Invalid value for the"
250                                 " '%s' parameter" % (name,))
251     return val
252
253   def _checkStringVariable(self, name, default=None):
254     """Return the parsed value of an int argument.
255
256     """
257     val = self.queryargs.get(name, default)
258     if isinstance(val, list):
259       if val:
260         val = val[0]
261       else:
262         val = default
263     return val
264
265   def getBodyParameter(self, name, *args):
266     """Check and return the value for a given parameter.
267
268     If a second parameter is not given, an error will be returned,
269     otherwise this parameter specifies the default value.
270
271     @param name: the required parameter
272
273     """
274     if name in self.req.request_body:
275       return self.req.request_body[name]
276     elif args:
277       return args[0]
278     else:
279       raise http.HttpBadRequest("Required parameter '%s' is missing" %
280                                 name)
281
282   def useLocking(self):
283     """Check if the request specifies locking.
284
285     """
286     return self._checkIntVariable('lock')
287
288   def useBulk(self):
289     """Check if the request specifies bulk querying.
290
291     """
292     return self._checkIntVariable('bulk')