ovirt-engine-sdk 4.0.1 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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