1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Remote API base resources library.
23
24 """
25
26
27
28
29
30 import logging
31
32 from ganeti import luxi
33 from ganeti import rapi
34 from ganeti import http
35 from ganeti import errors
36 from ganeti import compat
37
38
39
40 _DEFAULT = object()
41
42
43 _SUPPORTED_METHODS = frozenset([
44 http.HTTP_DELETE,
45 http.HTTP_GET,
46 http.HTTP_POST,
47 http.HTTP_PUT,
48 ])
49
50
52 """Builds list of attributes used for per-handler opcodes.
53
54 """
55 return [(method, "%s_OPCODE" % method, "%s_RENAME" % method,
56 "Get%sOpInput" % method.capitalize())
57 for method in _SUPPORTED_METHODS]
58
59
60 _OPCODE_ATTRS = _BuildOpcodeAttributes()
61
62
63 -def BuildUriList(ids, uri_format, uri_fields=("name", "uri")):
64 """Builds a URI list as used by index resources.
65
66 @param ids: list of ids as strings
67 @param uri_format: format to be applied for URI
68 @param uri_fields: optional parameter for field IDs
69
70 """
71 (field_id, field_uri) = uri_fields
72
73 def _MapId(m_id):
74 return {
75 field_id: m_id,
76 field_uri: uri_format % m_id,
77 }
78
79
80
81 ids.sort()
82
83 return map(_MapId, ids)
84
85
87 """Creates a list containing one column out of a list of lists.
88
89 @param sequence: sequence of lists
90 @param index: index of field
91
92 """
93 return map(lambda item: item[index], sequence)
94
95
97 """Maps two lists into one dictionary.
98
99 Example::
100 >>> MapFields(["a", "b"], ["foo", 123])
101 {'a': 'foo', 'b': 123}
102
103 @param names: field names (list of strings)
104 @param data: field data (list)
105
106 """
107 if len(names) != len(data):
108 raise AttributeError("Names and data must have the same length")
109 return dict(zip(names, data))
110
111
113 """Map value to field name in to one dictionary.
114
115 @param itemslist: a list of items values
116 @param fields: a list of items names
117
118 @return: a list of mapped dictionaries
119
120 """
121 items_details = []
122 for item in itemslist:
123 mapped = MapFields(fields, item)
124 items_details.append(mapped)
125 return items_details
126
127
129 """Makes params dictionary out of a option set.
130
131 This function returns a dictionary needed for hv or be parameters. But only
132 those fields which provided in the option set. Takes parameters frozensets
133 from constants.
134
135 @type opts: dict
136 @param opts: selected options
137 @type params: frozenset
138 @param params: subset of options
139 @rtype: dict
140 @return: dictionary of options, filtered by given subset.
141
142 """
143 result = {}
144
145 for p in params:
146 try:
147 value = opts[p]
148 except KeyError:
149 continue
150 result[p] = value
151
152 return result
153
154
156 """Fills an opcode with body parameters.
157
158 Parameter types are checked.
159
160 @type opcls: L{opcodes.OpCode}
161 @param opcls: Opcode class
162 @type body: dict
163 @param body: Body parameters as received from client
164 @type static: dict
165 @param static: Static parameters which can't be modified by client
166 @type rename: dict
167 @param rename: Renamed parameters, key as old name, value as new name
168 @return: Opcode object
169
170 """
171 if body is None:
172 params = {}
173 else:
174 CheckType(body, dict, "Body contents")
175
176
177 params = body.copy()
178
179 if rename:
180 for old, new in rename.items():
181 if new in params and old in params:
182 raise http.HttpBadRequest("Parameter '%s' was renamed to '%s', but"
183 " both are specified" %
184 (old, new))
185 if old in params:
186 assert new not in params
187 params[new] = params.pop(old)
188
189 if static:
190 overwritten = set(params.keys()) & set(static.keys())
191 if overwritten:
192 raise http.HttpBadRequest("Can't overwrite static parameters %r" %
193 overwritten)
194
195 params.update(static)
196
197
198 params = dict((str(key), value) for (key, value) in params.items())
199
200 try:
201 op = opcls(**params)
202 op.Validate(False)
203 except (errors.OpPrereqError, TypeError), err:
204 raise http.HttpBadRequest("Invalid body parameters: %s" % err)
205
206 return op
207
208
220
221
223 """Feedback logging function for jobs.
224
225 We don't have a stdout for printing log messages, so log them to the
226 http log at least.
227
228 @param msg: the message
229
230 """
231 (_, log_type, log_msg) = msg
232 logging.info("%s: %s", log_type, log_msg)
233
234
236 """Abort request if value type doesn't match expected type.
237
238 @param value: Value
239 @type exptype: type
240 @param exptype: Expected type
241 @type descr: string
242 @param descr: Description of value
243 @return: Value (allows inline usage)
244
245 """
246 if not isinstance(value, exptype):
247 raise http.HttpBadRequest("%s: Type is '%s', but '%s' is expected" %
248 (descr, type(value).__name__, exptype.__name__))
249
250 return value
251
252
254 """Check and return the value for a given parameter.
255
256 If no default value was given and the parameter doesn't exist in the input
257 data, an error is raise.
258
259 @type data: dict
260 @param data: Dictionary containing input data
261 @type name: string
262 @param name: Parameter name
263 @param default: Default value (can be None)
264 @param exptype: Expected type (can be None)
265
266 """
267 try:
268 value = data[name]
269 except KeyError:
270 if default is not _DEFAULT:
271 return default
272
273 raise http.HttpBadRequest("Required parameter '%s' is missing" %
274 name)
275
276 if exptype is _DEFAULT:
277 return value
278
279 return CheckType(value, exptype, "'%s' parameter" % name)
280
281
283 """Generic class for resources.
284
285 """
286
287 GET_ACCESS = []
288 PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE]
289 POST_ACCESS = [rapi.RAPI_ACCESS_WRITE]
290 DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE]
291
292 - def __init__(self, items, queryargs, req, _client_cls=None):
293 """Generic resource constructor.
294
295 @param items: a list with variables encoded in the URL
296 @param queryargs: a dictionary with additional options from URL
297 @param req: Request context
298 @param _client_cls: L{luxi} client class (unittests only)
299
300 """
301 self.items = items
302 self.queryargs = queryargs
303 self._req = req
304
305 if _client_cls is None:
306 _client_cls = luxi.Client
307
308 self._client_cls = _client_cls
309
310 - def _GetRequestBody(self):
311 """Returns the body data.
312
313 """
314 return self._req.private.body_data
315
316 request_body = property(fget=_GetRequestBody)
317
319 """Return the parsed value of an int argument.
320
321 """
322 val = self.queryargs.get(name, default)
323 if isinstance(val, list):
324 if val:
325 val = val[0]
326 else:
327 val = default
328 try:
329 val = int(val)
330 except (ValueError, TypeError):
331 raise http.HttpBadRequest("Invalid value for the"
332 " '%s' parameter" % (name,))
333 return val
334
336 """Return the parsed value of an int argument.
337
338 """
339 val = self.queryargs.get(name, default)
340 if isinstance(val, list):
341 if val:
342 val = val[0]
343 else:
344 val = default
345 return val
346
347 - def getBodyParameter(self, name, *args):
348 """Check and return the value for a given parameter.
349
350 If a second parameter is not given, an error will be returned,
351 otherwise this parameter specifies the default value.
352
353 @param name: the required parameter
354
355 """
356 if args:
357 return CheckParameter(self.request_body, name, default=args[0])
358
359 return CheckParameter(self.request_body, name)
360
362 """Check if the request specifies locking.
363
364 """
365 return bool(self._checkIntVariable("lock"))
366
368 """Check if the request specifies bulk querying.
369
370 """
371 return bool(self._checkIntVariable("bulk"))
372
374 """Check if the request specifies a forced operation.
375
376 """
377 return bool(self._checkIntVariable("force"))
378
380 """Check if the request specifies dry-run mode.
381
382 """
383 return bool(self._checkIntVariable("dry-run"))
384
397
425
426
428 """Returns all opcodes used by a resource.
429
430 """
431 return frozenset(filter(None, (getattr(cls, op_attr, None)
432 for (_, op_attr, _, _) in _OPCODE_ATTRS)))
433
434
466
467
469 """Base class for opcode-based RAPI resources.
470
471 Instances of this class automatically gain handler functions through
472 L{_MetaOpcodeResource} for any method for which a C{$METHOD$_OPCODE} variable
473 is defined at class level. Subclasses can define a C{Get$Method$OpInput}
474 method to do their own opcode input processing (e.g. for static values). The
475 C{$METHOD$_RENAME} variable defines which values are renamed (see
476 L{baserlib.FillOpcode}).
477
478 @cvar GET_OPCODE: Set this to a class derived from L{opcodes.OpCode} to
479 automatically generate a GET handler submitting the opcode
480 @cvar GET_RENAME: Set this to rename parameters in the GET handler (see
481 L{baserlib.FillOpcode})
482 @ivar GetGetOpInput: Define this to override the default method for
483 getting opcode parameters (see L{baserlib.OpcodeResource._GetDefaultData})
484
485 @cvar PUT_OPCODE: Set this to a class derived from L{opcodes.OpCode} to
486 automatically generate a PUT handler submitting the opcode
487 @cvar PUT_RENAME: Set this to rename parameters in the PUT handler (see
488 L{baserlib.FillOpcode})
489 @ivar GetPutOpInput: Define this to override the default method for
490 getting opcode parameters (see L{baserlib.OpcodeResource._GetDefaultData})
491
492 @cvar POST_OPCODE: Set this to a class derived from L{opcodes.OpCode} to
493 automatically generate a POST handler submitting the opcode
494 @cvar POST_RENAME: Set this to rename parameters in the DELETE handler (see
495 L{baserlib.FillOpcode})
496 @ivar GetPostOpInput: Define this to override the default method for
497 getting opcode parameters (see L{baserlib.OpcodeResource._GetDefaultData})
498
499 @cvar DELETE_OPCODE: Set this to a class derived from L{opcodes.OpCode} to
500 automatically generate a GET handler submitting the opcode
501 @cvar DELETE_RENAME: Set this to rename parameters in the DELETE handler (see
502 L{baserlib.FillOpcode})
503 @ivar GetDeleteOpInput: Define this to override the default method for
504 getting opcode parameters (see L{baserlib.OpcodeResource._GetDefaultData})
505
506 """
507 __metaclass__ = _MetaOpcodeResource
508
511
513 (body, static) = fn()
514 op = FillOpcode(opcode, body, static, rename=rename)
515 return self.SubmitJob([op])
516