4 # Copyright (C) 2006, 2007, 2008 Google Inc.
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.
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.
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
22 """Remote API base resources library.
26 # pylint: disable-msg=C0103
28 # C0103: Invalid name, since the R_* names are not conforming
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
41 def BuildUriList(ids, uri_format, uri_fields=("name", "uri")):
42 """Builds a URI list as used by index resources.
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
49 (field_id, field_uri) = uri_fields
52 return { field_id: m_id, field_uri: uri_format % m_id, }
54 # Make sure the result is sorted, makes it nicer to look at and simplifies
58 return map(_MapId, ids)
61 def ExtractField(sequence, index):
62 """Creates a list containing one column out of a list of lists.
64 @param sequence: sequence of lists
65 @param index: index of field
68 return map(lambda item: item[index], sequence)
71 def MapFields(names, data):
72 """Maps two lists into one dictionary.
75 >>> MapFields(["a", "b"], ["foo", 123])
76 {'a': 'foo', 'b': 123}
78 @param names: field names (list of strings)
79 @param data: field data (list)
82 if len(names) != len(data):
83 raise AttributeError("Names and data must have the same length")
84 return dict(zip(names, data))
87 def _Tags_GET(kind, name):
88 """Helper function to retrieve tags.
91 if kind == constants.TAG_INSTANCE or kind == constants.TAG_NODE:
93 raise http.HttpBadRequest("Missing name on tag request")
95 if kind == constants.TAG_INSTANCE:
96 fn = cl.QueryInstances
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")
103 elif kind == constants.TAG_CLUSTER:
104 ssc = ssconf.SimpleStore()
105 tags = ssc.GetClusterTags()
110 def _Tags_PUT(kind, tags, name, dry_run):
111 """Helper function to set tags.
114 return SubmitJob([opcodes.OpAddTags(kind=kind, name=name,
115 tags=tags, dry_run=dry_run)])
118 def _Tags_DELETE(kind, tags, name, dry_run):
119 """Helper function to delete tags.
122 return SubmitJob([opcodes.OpDelTags(kind=kind, name=name,
123 tags=tags, dry_run=dry_run)])
126 def MapBulkFields(itemslist, fields):
127 """Map value to field name in to one dictionary.
129 @param itemslist: a list of items values
130 @param fields: a list of items names
132 @return: a list of mapped dictionaries
136 for item in itemslist:
137 mapped = MapFields(fields, item)
138 items_details.append(mapped)
142 def MakeParamsDict(opts, params):
143 """Makes params dictionary out of a option set.
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
150 @param opts: selected options
151 @type params: frozenset
152 @param params: subset of options
154 @return: dictionary of options, filtered by given subset.
169 def SubmitJob(op, cl=None):
170 """Generic wrapper for submit job, for better http compatibility.
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
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))
195 """Geric wrapper for luxi.Client(), for better http compatiblity.
200 except luxi.NoMasterError, err:
201 raise http.HttpBadGateway("Master seems to unreachable: %s" % str(err))
204 def FeedbackFn(ts, log_type, log_msg): # pylint: disable-msg=W0613
205 """Feedback logging function for http case.
207 We don't have a stdout for printing log messages, so log them to the
210 @param ts: the timestamp (unused)
213 logging.info("%s: %s", log_type, log_msg)
216 class R_Generic(object):
217 """Generic class for resources.
220 # Default permission requirements
222 PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE]
223 POST_ACCESS = [rapi.RAPI_ACCESS_WRITE]
224 DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE]
226 def __init__(self, items, queryargs, req):
227 """Generic resource constructor.
229 @param items: a list with variables encoded in the URL
230 @param queryargs: a dictionary with additional options from URL
234 self.queryargs = queryargs
238 def getSerialNumber(self):
239 """Get Serial Number.
244 def _checkIntVariable(self, name, default=0):
245 """Return the parsed value of an int argument.
248 val = self.queryargs.get(name, default)
249 if isinstance(val, list):
256 except (ValueError, TypeError):
257 raise http.HttpBadRequest("Invalid value for the"
258 " '%s' parameter" % (name,))
261 def _checkStringVariable(self, name, default=None):
262 """Return the parsed value of an int argument.
265 val = self.queryargs.get(name, default)
266 if isinstance(val, list):
273 def getBodyParameter(self, name, *args):
274 """Check and return the value for a given parameter.
276 If a second parameter is not given, an error will be returned,
277 otherwise this parameter specifies the default value.
279 @param name: the required parameter
282 if name in self.req.request_body:
283 return self.req.request_body[name]
287 raise http.HttpBadRequest("Required parameter '%s' is missing" %
290 def useLocking(self):
291 """Check if the request specifies locking.
294 return self._checkIntVariable('lock')
297 """Check if the request specifies bulk querying.
300 return self._checkIntVariable('bulk')
303 """Check if the request specifies a forced operation.
306 return self._checkIntVariable('force')
309 """Check if the request specifies dry-run mode.
312 return self._checkIntVariable('dry-run')