ovirt-engine-sdk 4.0.1 → 4.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2015-2016 Red Hat, Inc.
2
+ # Copyright (c) 2015-2017 Red Hat, Inc.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -15,43 +15,77 @@
15
15
  #
16
16
 
17
17
  module OvirtSDK4
18
+ #
19
+ # Instances of this class are returned for operatoins that specify the `wait: false` parameter.
20
+ #
21
+ class Future
22
+ #
23
+ # Creates a new future result.
24
+ #
25
+ # @param service [Service] The service that created this future.
26
+ # @param request [HttpRequest] The request that this future will wait for when the `wait` method is called.
27
+ # @param block [Block] The block that will be executed to check the response, and to convert its body into the
28
+ # right type of object.
29
+ #
30
+ # @api private
31
+ #
32
+ def initialize(service, request, &block)
33
+ @service = service
34
+ @request = request
35
+ @block = block
36
+ end
37
+
38
+ #
39
+ # Waits till the result of the operation that created this future is available.
40
+ #
41
+ # @return [Object] The result of the operation that created this future.
42
+ #
43
+ def wait
44
+ response = @service.connection.wait(@request)
45
+ raise response if response.is_a?(Exception)
46
+
47
+ @block.call(response)
48
+ end
49
+
50
+ #
51
+ # Returns a string representation of the future.
52
+ #
53
+ # @return [String] The string representation.
54
+ #
55
+ def inspect
56
+ "#<#{self.class.name}:#{@request.method} #{@request.url}>"
57
+ end
58
+
59
+ #
60
+ # Returns a string representation of the future.
61
+ #
62
+ # @return [String] The string representation.
63
+ #
64
+ def to_s
65
+ inspect
66
+ end
67
+ end
18
68
 
19
69
  #
20
70
  # This is the base class for all the services of the SDK. It contains the utility methods used by all of them.
21
71
  #
22
72
  class Service
23
-
24
73
  #
25
- # Creates and raises an error containing the details of the given HTTP response and fault.
74
+ # Creates a new implementation of the service.
26
75
  #
27
- # This method is intended for internal use by other components of the SDK. Refrain from using it directly, as
28
- # backwards compatibility isn't guaranteed.
76
+ # @param parent [Service, Connection] The parent of this service. For most services the parent will be another
77
+ # service. For example, for the `vm` service that manages virtual machine `123` the parent will be the `vms`
78
+ # service that manages the collection of virtual machines. For the root of the services tree the parent will
79
+ # be the connection.
80
+ #
81
+ # @param path [String] The path of this service, relative to its parent. For example, the path of the `vm`
82
+ # service that manages virtual machine `123` will be `vm/123`.
29
83
  #
30
84
  # @api private
31
85
  #
32
- def raise_error(response, fault)
33
- message = ''
34
- unless fault.nil?
35
- unless fault.reason.nil?
36
- message << ' ' unless message.empty?
37
- message << "Fault reason is \"#{fault.reason}\"."
38
- end
39
- unless fault.detail.nil?
40
- message << ' ' unless message.empty?
41
- message << "Fault detail is \"#{fault.detail}\"."
42
- end
43
- end
44
- unless response.nil?
45
- unless response.code.nil?
46
- message << ' ' unless message.empty?
47
- message << "HTTP response code is #{response.code}."
48
- end
49
- unless response.message.nil?
50
- message << ' ' unless message.empty?
51
- message << "HTTP response message is \"#{response.message}\"."
52
- end
53
- end
54
- raise Error.new(message)
86
+ def initialize(parent, path)
87
+ @parent = parent
88
+ @path = path
55
89
  end
56
90
 
57
91
  #
@@ -63,15 +97,9 @@ module OvirtSDK4
63
97
  # @api private
64
98
  #
65
99
  def check_fault(response)
66
- body = response.body
67
- if body.nil? || body.length == 0
68
- raise_error(response, nil)
69
- end
70
- body = Reader.read(body)
71
- if body.is_a?(Fault)
72
- raise_error(response, body)
73
- end
74
- raise Error.new("Expected a fault, but got '#{body.class.name.split('::').last}'")
100
+ body = internal_read_body(response)
101
+ connection.raise_error(response, body) if body.is_a?(Fault)
102
+ raise Error, "Expected a fault, but got '#{body.class.name.split('::').last}'"
75
103
  end
76
104
 
77
105
  #
@@ -85,21 +113,384 @@ module OvirtSDK4
85
113
  # @api private
86
114
  #
87
115
  def check_action(response)
88
- body = response.body
89
- if body.nil? || body.length == 0
90
- raise_error(response, nil)
91
- end
92
- body = Reader.read(body)
93
- if body.is_a?(Fault)
94
- raise_error(response, body)
95
- end
116
+ body = internal_read_body(response)
117
+ connection.raise_error(response, body) if body.is_a?(Fault)
96
118
  if body.is_a?(Action)
97
119
  return body if body.fault.nil?
98
- raise_error(response, body.fault)
120
+
121
+ connection.raise_error(response, body.fault)
99
122
  end
100
- raise Error.new("Expected an action or a fault, but got '#{body.class.name.split('::').last}'")
123
+ raise Error, "Expected an action or a fault, but got '#{body.class.name.split('::').last}'"
101
124
  end
102
125
 
103
- end
126
+ #
127
+ # Returns the connection used by this service.
128
+ #
129
+ # This method is intended for internal use by other components of the SDK. Refrain from using it directly, as
130
+ # backwards compatibility isn't guaranteed.
131
+ #
132
+ # @return [Connection] The connection used by this service.
133
+ #
134
+ # @api private
135
+ #
136
+ def connection
137
+ return @parent if @parent.is_a? Connection
138
+
139
+ @parent.connection
140
+ end
141
+
142
+ #
143
+ # Returns a string representation of the service.
144
+ #
145
+ # @return [String] The string representation.
146
+ #
147
+ def inspect
148
+ "#<#{self.class.name}:#{absolute_path}>"
149
+ end
150
+
151
+ #
152
+ # Returns a string representation of the service.
153
+ #
154
+ # @return [String] The string representation.
155
+ #
156
+ def to_s
157
+ inspect
158
+ end
159
+
160
+ protected
161
+
162
+ #
163
+ # Executes a `get` method.
164
+ #
165
+ # @param specs [Array<Array<Symbol, Class>>] An array of arrays containing the names and types of the parameters.
166
+ # @param opts [Hash] The hash containing the values of the parameters.
167
+ #
168
+ # @api private
169
+ #
170
+ def internal_get(specs, opts)
171
+ # Get the values of the built-in options:
172
+ headers = opts.delete(:headers) || {}
173
+ query = opts.delete(:query) || {}
174
+ timeout = opts.delete(:timeout)
175
+ wait = opts.delete(:wait)
176
+ wait = true if wait.nil?
177
+
178
+ # Get the values of the options specific to this operation:
179
+ specs.each do |name, kind|
180
+ value = opts.delete(name)
181
+ query[name] = Writer.render(value, kind) unless value.nil?
182
+ end
183
+
184
+ # Check the remaining options:
185
+ check_bad_opts(specs, opts)
186
+
187
+ # Create and send the request:
188
+ request = HttpRequest.new
189
+ request.method = :GET
190
+ request.url = absolute_path
191
+ request.headers = headers
192
+ request.query = query
193
+ request.timeout = timeout
194
+ connection.send(request)
195
+ result = Future.new(self, request) do |response|
196
+ raise response if response.is_a?(Exception)
197
+
198
+ case response.code
199
+ when 200
200
+ internal_read_body(response)
201
+ else
202
+ check_fault(response)
203
+ end
204
+ end
205
+ result = result.wait if wait
206
+ result
207
+ end
208
+
209
+ #
210
+ # Executes an `add` method.
211
+ #
212
+ # @param object [Object] The added object.
213
+ # @param type [Class] Type type of the added object.
214
+ # @param specs [Array<Array<Symbol, Class>>] An array of arrays containing the names and types of the parameters.
215
+ # @param opts [Hash] The hash containing the values of the parameters.
216
+ #
217
+ # @api private
218
+ #
219
+ def internal_add(object, type, specs, opts)
220
+ # Get the values of the built-in options:
221
+ object = type.new(object) if object.is_a?(Hash)
222
+ headers = opts.delete(:headers) || {}
223
+ query = opts.delete(:query) || {}
224
+ timeout = opts.delete(:timeout)
225
+ wait = opts.delete(:wait)
226
+ wait = true if wait.nil?
227
+
228
+ # Get the values of the options specific to this operation:
229
+ specs.each do |name, kind|
230
+ value = opts.delete(name)
231
+ query[name] = Writer.render(value, kind) unless value.nil?
232
+ end
233
+
234
+ # Check the remaining options:
235
+ check_bad_opts(specs, opts)
236
+
237
+ # Create and send the request:
238
+ request = HttpRequest.new
239
+ request.method = :POST
240
+ request.url = absolute_path
241
+ request.headers = headers
242
+ request.query = query
243
+ request.body = Writer.write(object, indent: true)
244
+ request.timeout = timeout
245
+ connection.send(request)
246
+ result = Future.new(self, request) do |response|
247
+ raise response if response.is_a?(Exception)
248
+
249
+ case response.code
250
+ when 200, 201, 202
251
+ internal_read_body(response)
252
+ else
253
+ check_fault(response)
254
+ end
255
+ end
256
+ result = result.wait if wait
257
+ result
258
+ end
259
+
260
+ #
261
+ # Executes an `update` method.
262
+ #
263
+ # @param object [Object] The updated object.
264
+ # @param type [Class] Type type of the updated object.
265
+ # @param specs [Array<Array<Symbol, Class>>] An array of tuples containing the names and types of the parameters.
266
+ # @param opts [Hash] The hash containing the values of the parameters.
267
+ #
268
+ # @api private
269
+ #
270
+ def internal_update(object, type, specs, opts)
271
+ # get the values of the built-in options:
272
+ object = type.new(object) if object.is_a?(Hash)
273
+ headers = opts.delete(:headers) || {}
274
+ query = opts.delete(:query) || {}
275
+ timeout = opts.delete(:timeout)
276
+ wait = opts.delete(:wait)
277
+ wait = true if wait.nil?
278
+
279
+ # Get the values of the options specific to this operation:
280
+ specs.each do |name, kind|
281
+ value = opts.delete(name)
282
+ query[name] = Writer.render(value, kind) unless value.nil?
283
+ end
284
+
285
+ # Check the remaining options:
286
+ check_bad_opts(specs, opts)
287
+
288
+ # Create and send the request:
289
+ request = HttpRequest.new
290
+ request.method = :PUT
291
+ request.url = absolute_path
292
+ request.headers = headers
293
+ request.query = query
294
+ request.body = Writer.write(object, indent: true)
295
+ request.timeout = timeout
296
+ connection.send(request)
297
+ result = Future.new(self, request) do |response|
298
+ raise response if response.is_a?(Exception)
299
+
300
+ case response.code
301
+ when 200
302
+ internal_read_body(response)
303
+ else
304
+ check_fault(response)
305
+ end
306
+ end
307
+ result = result.wait if wait
308
+ result
309
+ end
104
310
 
311
+ #
312
+ # Executes a `remove` method.
313
+ #
314
+ # @param specs [Array<Array<Symbol, Class>>] An array of tuples containing the names and types of the parameters.
315
+ # @param opts [Hash] The hash containing the values of the parameters.
316
+ #
317
+ # @api private
318
+ #
319
+ def internal_remove(specs, opts)
320
+ # Get the values of the built-in options:
321
+ headers = opts.delete(:headers) || {}
322
+ query = opts.delete(:query) || {}
323
+ timeout = opts.delete(:timeout)
324
+ wait = opts.delete(:wait)
325
+ wait = true if wait.nil?
326
+
327
+ # Get the values of the options specific to this operation:
328
+ specs.each do |name, kind|
329
+ value = opts.delete(name)
330
+ query[name] = Writer.render(value, kind) unless value.nil?
331
+ end
332
+
333
+ # Check the remaining options:
334
+ check_bad_opts(specs, opts)
335
+
336
+ # Create and send the request:
337
+ request = HttpRequest.new
338
+ request.method = :DELETE
339
+ request.url = absolute_path
340
+ request.headers = headers
341
+ request.query = query
342
+ request.timeout = timeout
343
+ connection.send(request)
344
+ result = Future.new(self, request) do |response|
345
+ raise response if response.is_a?(Exception)
346
+
347
+ check_fault(response) unless response.code == 200
348
+ end
349
+ result = result.wait if wait
350
+ result
351
+ end
352
+
353
+ #
354
+ # Executes an action method.
355
+ #
356
+ # @param name [Symbol] The name of the action, for example `:start`.
357
+ # @param member [Symbol] The name of the action member that contains the result. For example `:is_attached`. Can
358
+ # be `nil` if the action doesn't return any value.
359
+ # @param specs [Array<Array<Symbol, Class>>] An array of tuples containing the names and types of the parameters.
360
+ # @param opts [Hash] The hash containing the parameters of the action.
361
+ #
362
+ # @api private
363
+ #
364
+ def internal_action(name, member, specs, opts)
365
+ # Get the values of the built-in options:
366
+ headers = opts.delete(:headers) || {}
367
+ query = opts.delete(:query) || {}
368
+ timeout = opts.delete(:timeout)
369
+ wait = opts.delete(:wait)
370
+ wait = true if wait.nil?
371
+
372
+ # Create the action:
373
+ action = Action.new(opts)
374
+
375
+ # The constructor of the action doesn't remove the options that it uses, so we need to remove them explicitly
376
+ # before checking for bad options.
377
+ specs.each_entry do |key, _|
378
+ opts.delete(key)
379
+ end
380
+ check_bad_opts(specs, opts)
381
+
382
+ # Create and send the request:
383
+ request = HttpRequest.new
384
+ request.method = :POST
385
+ request.url = "#{absolute_path}/#{name}"
386
+ request.headers = headers
387
+ request.query = query
388
+ request.body = Writer.write(action, indent: true)
389
+ request.timeout = timeout
390
+ connection.send(request)
391
+ result = Future.new(self, request) do |response|
392
+ raise response if response.is_a?(Exception)
393
+
394
+ case response.code
395
+ when 200, 201, 202
396
+ action = check_action(response)
397
+ action.send(member) if member
398
+ else
399
+ check_action(response)
400
+ end
401
+ end
402
+ result = result.wait if wait
403
+ result
404
+ end
405
+
406
+ #
407
+ # Checks the content type of the given response, and if it is XML, as expected, reads the body and converts it
408
+ # to an object. If it isn't XML, then it raises an exception.
409
+ #
410
+ # @param response [HttpResponse] The HTTP response to check.
411
+ # @return [Object] The result of converting the HTTP response body from XML to an SDK object.
412
+ #
413
+ # @api private
414
+ #
415
+ def internal_read_body(response)
416
+ # First check if the response body is empty, as it makes no sense to check the content type if there is
417
+ # no body:
418
+ connection.raise_error(response, 'The response body is empty') if response.body.nil? || response.body.length.zero?
419
+
420
+ # Check the content type, as otherwise the parsing will fail, and the resulting error message won't be explicit
421
+ # about the cause of the problem:
422
+ connection.check_xml_content_type(response)
423
+
424
+ # Parse the XML and generate the SDK object:
425
+ Reader.read(response.body)
426
+ end
427
+
428
+ #
429
+ # Returns the absolute path of this service.
430
+ #
431
+ # @return [String] The absolute path of this service. For example, the path of the `vm` service that manages
432
+ # virtual machine `123` will be `vms/123`. Note that this absolute path doesn't include the `/ovirt-engine/api/'
433
+ # prefix.
434
+ #
435
+ # @api private
436
+ #
437
+ def absolute_path
438
+ return @path if @parent.is_a? Connection
439
+
440
+ prefix = @parent.absolute_path
441
+ return @path if prefix.empty?
442
+
443
+ "#{prefix}/#{@path}"
444
+ end
445
+
446
+ private
447
+
448
+ #
449
+ # Checks if the given hash contains any value, and if it does raises an exception indicating that they are not
450
+ # supported.
451
+ #
452
+ # @param specs [Array<Array<Symbol, Class>>] An array of tuples containing the names and types of the parameters.
453
+ # @param opts [Hash] The hash containing the values of the parameters.
454
+ #
455
+ def check_bad_opts(specs, opts)
456
+ return if opts.empty?
457
+
458
+ bad_names = opts.keys
459
+ bad_text = nice_list(bad_names)
460
+ if bad_names.length > 1
461
+ message = "The options #{bad_text} aren't supported."
462
+ else
463
+ message = "The option #{bad_text} isn't supported."
464
+ end
465
+ good_names = specs.map(&:first)
466
+ unless good_names.empty?
467
+ good_text = nice_list(good_names)
468
+ if good_names.length > 1
469
+ message << " The supported options are #{good_text}."
470
+ else
471
+ message << " The only supported option is #{good_text}."
472
+ end
473
+ end
474
+ raise Error, message
475
+ end
476
+
477
+ #
478
+ # Generates a human readable list containing the names of the given symbols.
479
+ #
480
+ # @param items [Array<Symbol>]
481
+ # @return [String] An string containing the names of the symbols, sorted, quoted, and in a gramatically correct
482
+ # format.
483
+ #
484
+ def nice_list(items)
485
+ return nil if items.empty?
486
+
487
+ items = items.sort
488
+ items = items.map { |item| "'#{item}'" }
489
+ return items.first if items.length == 1
490
+
491
+ head = items[0, items.length - 1].join(', ')
492
+ tail = items.last
493
+ "#{head} and #{tail}"
494
+ end
495
+ end
105
496
  end