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.
@@ -0,0 +1,70 @@
1
+ #
2
+ # Copyright (c) 2017 Red Hat, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module OvirtSDK4
18
+ #
19
+ # The base class for all errors raised by the SDK.
20
+ #
21
+ class Error
22
+ #
23
+ # An error code associated to the error. For HTTP related errors, this will be the HTTP response code returned by
24
+ # the server. For example, if retrieving of a virtual machine fails because it doesn't exist this attribute will
25
+ # contain the integer value 404. Note that this may be `nil` if the error is not HTTP related.
26
+ #
27
+ # @return [Integer] The HTTP error code.
28
+ #
29
+ attr_accessor :code
30
+
31
+ #
32
+ # The `Fault` object associated to the error.
33
+ #
34
+ # @return [Fault] The fault object associated to the error, if a fault was provided by the server, `nil` otherwise.
35
+ #
36
+ attr_accessor :fault
37
+ end
38
+
39
+ #
40
+ # This class of error indicates that an authentiation or authorization problem happenend, like incorrect user name,
41
+ # incorrect password, or missing permissions.
42
+ #
43
+ class AuthError < Error
44
+ end
45
+
46
+ #
47
+ # This class of error indicates that the name of the server or the name of the proxy can't be resolved to an IP
48
+ # address, or that the connection can't be stablished because the server is down or unreachable.
49
+ #
50
+ # Note that for this class of error the `code` and `fault` attributes will always be empty, as no response from the
51
+ # server will be available to populate them.
52
+ #
53
+ class ConnectionError < Error
54
+ end
55
+
56
+ #
57
+ # This class of error indicates that an object can't be found.
58
+ #
59
+ class NotFoundError < Error
60
+ end
61
+
62
+ #
63
+ # This class of error indicates that an operation timed out.
64
+ #
65
+ # Note that for this class of error the `code` and `fault` attributes will always be empty, as no response from the
66
+ # server will be available to populate them.
67
+ #
68
+ class TimeoutError < Error
69
+ end
70
+ end
@@ -0,0 +1,324 @@
1
+ #
2
+ # Copyright (c) 2016-2017 Red Hat, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module OvirtSDK4
18
+ #
19
+ # This class is used to probe the engine to find which API versions it supports.
20
+ #
21
+ class Probe
22
+ #
23
+ # This class method receives a set of options that define the server to probe and returns an arrays of objects of
24
+ # the OvirtSDK4::ProbeResult class containing the results of the probe.
25
+ #
26
+ # @param opts [Hash] The options used to create the probe.
27
+ #
28
+ # @option opts [String] :host The name or IP address of the host to probe.
29
+ #
30
+ # @option opts [Integer] :port (443) The port number to probe.
31
+ #
32
+ # @option opts [String] :username The name of the user, something like `admin@internal`.
33
+ #
34
+ # @option opts [String] :password The password of the user.
35
+ #
36
+ # @option opts [Boolean] :insecure (false) A boolean flag that indicates if the server TLS certificate and host
37
+ # name should be checked.
38
+ #
39
+ # @option opts [String] :ca_file The name of a PEM file containing the trusted CA certificates. The certificate
40
+ # presented by the server will be verified using these CA certificates. If not set then the system wide CA
41
+ # certificates store is used.
42
+ #
43
+ # @option opts [String] :log The logger where the log messages will be written.
44
+ #
45
+ # @option opts [Boolean] :debug (false) A boolean flag indicating if debug output should be generated. If the
46
+ # values is `true` and the `log` parameter isn't `nil` then the data sent to and received from the server will
47
+ # be written to the log. Be aware that user names and passwords will also be written, so handle with care.
48
+ #
49
+ # @option opts [String] :proxy_url A string containing the protocol, address and port number of the proxy server
50
+ # to use to connect to the server. For example, in order to use the HTTP proxy `proxy.example.com` that is
51
+ # listening on port `3128` the value should be `http://proxy.example.com:3128`. This is optional, and if not
52
+ # given the connection will go directly to the server specified in the `url` parameter.
53
+ #
54
+ # @option opts [String] :proxy_username The name of the user to authenticate to the proxy server.
55
+ #
56
+ # @option opts [String] :proxy_password The password of the user to authenticate to the proxy server.
57
+ #
58
+ # @return [Array<ProbeResult>] An array of objects of the OvirtSDK4::ProbeResult class.
59
+ #
60
+ def self.probe(opts)
61
+ probe = nil
62
+ begin
63
+ probe = Probe.new(opts)
64
+ probe.probe
65
+ ensure
66
+ probe.close if probe
67
+ end
68
+ end
69
+
70
+ ENGINE_CERTIFICATE_PATH =
71
+ '/ovirt-engine/services/pki-resource?resource=engine-certificate&format=OPENSSH-PUBKEY'.freeze
72
+
73
+ #
74
+ # This class method receives a set of options that define the server to probe and returns a boolean value
75
+ # that represents whether an oVirt instance was detected.
76
+ #
77
+ # @param opts [Hash] The options used to create the probe.
78
+ #
79
+ # @option opts [String] :host The name or IP address of the host to probe.
80
+ #
81
+ # @option opts [Integer] :port (443) The port number to probe.
82
+ #
83
+ # @option opts [String] :log The logger where the log messages will be written.
84
+ #
85
+ # @option opts [Boolean] :debug (false) A boolean flag indicating if debug output should be generated. If the
86
+ # values is `true` and the `log` parameter isn't `nil` then the data sent to and received from the server will
87
+ # be written to the log. Be aware that user names and passwords will also be written, so handle with care.
88
+ #
89
+ # @option opts [String] :proxy_url A string containing the protocol, address and port number of the proxy server
90
+ # to use to connect to the server. For example, in order to use the HTTP proxy `proxy.example.com` that is
91
+ # listening on port `3128` the value should be `http://proxy.example.com:3128`. This is optional, and if not
92
+ # given the connection will go directly to the server specified in the `url` parameter.
93
+ #
94
+ # @option opts [Integer] :timeout (0) Set a connection timeout, in seconds. If the value is 0 no timeout is set.
95
+ #
96
+ # @return [Boolean] Boolean value, `true` if an oVirt instance was detected.
97
+ #
98
+ def self.exists?(opts)
99
+ probe = nil
100
+ begin
101
+ opts[:insecure] = true
102
+ probe = Probe.new(opts)
103
+ probe.exists?
104
+ ensure
105
+ probe.close if probe
106
+ end
107
+ end
108
+
109
+ #
110
+ # Creates a new probe.
111
+ #
112
+ # @param opts [Hash] The options used to create the probe.
113
+ #
114
+ # @option opts [String] :host The name or IP address of the host to probe.
115
+ #
116
+ # @option opts [Integer] :port (443) The port number to probe.
117
+ #
118
+ # @option opts [String] :username The name of the user, something like `admin@internal`.
119
+ #
120
+ # @option opts [String] :password The password of the user.
121
+ #
122
+ # @option opts [Boolean] :insecure (false) A boolean flag that indicates if the server TLS certificate and host
123
+ # name should be checked.
124
+ #
125
+ # @option opts [String] :ca_file The name of a PEM file containing the trusted CA certificates. The certificate
126
+ # presented by the server will be verified using these CA certificates. If not set then the system wide CA
127
+ # certificates store is used.
128
+ #
129
+ # @option opts [String] :log The logger where the log messages will be written.
130
+ #
131
+ # @option opts [Boolean] :debug (false) A boolean flag indicating if debug output should be generated. If the
132
+ # values is `true` and the `log` parameter isn't `nil` then the data sent to and received from the server will
133
+ # be written to the log. Be aware that user names and passwords will also be written, so handle with care.
134
+ #
135
+ # @option opts [String] :proxy_url A string containing the protocol, address and port number of the proxy server
136
+ # to use to connect to the server. For example, in order to use the HTTP proxy `proxy.example.com` that is
137
+ # listening on port `3128` the value should be `http://proxy.example.com:3128`. This is optional, and if not
138
+ # given the connection will go directly to the server specified in the `url` parameter.
139
+ #
140
+ # @option opts [String] :proxy_username The name of the user to authenticate to the proxy server.
141
+ #
142
+ # @option opts [String] :proxy_password The password of the user to authenticate to the proxy server.
143
+ #
144
+ # @api private
145
+ #
146
+ def initialize(opts)
147
+ # Get the options and assign default values:
148
+ @host = opts[:host]
149
+ @port = opts[:port] || 443
150
+ @username = opts[:username]
151
+ @password = opts[:password]
152
+ @insecure = opts[:insecure] || false
153
+ @ca_file = opts[:ca_file]
154
+ @log = opts[:log]
155
+ @debug = opts[:debug] || false
156
+ @proxy_url = opts[:proxy_url]
157
+ @proxy_username = opts[:proxy_username]
158
+ @proxy_password = opts[:proxy_password]
159
+ @timeout = opts[:timeout]
160
+
161
+ # Create the HTTP client:
162
+ @client = HttpClient.new(
163
+ host: @host,
164
+ port: @port,
165
+ insecure: @insecure,
166
+ ca_file: @ca_file,
167
+ log: @log,
168
+ debug: @debug,
169
+ proxy_url: @proxy_url,
170
+ proxy_username: @proxy_username,
171
+ proxy_password: @proxy_password,
172
+ timeout: @timeout
173
+ )
174
+ end
175
+
176
+ #
177
+ # Probes the server to detect the supported versions of the API.
178
+ #
179
+ # @return [Array<ProbeResult>] An array of objects of the OvirtSDK4::ProbeResult class.
180
+ #
181
+ # @api private
182
+ #
183
+ def probe
184
+ path = detect_path
185
+ raise Error, 'API path not found' unless path
186
+
187
+ detect_version(path).map { |version| ProbeResult.new(version: version) }
188
+ end
189
+
190
+ #
191
+ # Probes the server to detect if it has an ovirt instance running on it
192
+ #
193
+ # @return [Boolean] `true` if oVirt instance was detected, false otherwise
194
+ #
195
+ # @api private
196
+ #
197
+ def exists?
198
+ response = send(path: ENGINE_CERTIFICATE_PATH)
199
+ response.code == 200
200
+ end
201
+
202
+ #
203
+ # Releases the resources used by this probe.
204
+ #
205
+ # @api private
206
+ #
207
+ def close
208
+ # Close the HTTP client:
209
+ @client.close if @client
210
+ end
211
+
212
+ private
213
+
214
+ #
215
+ # We will only check these paths, as there is where the API is available in common installations of the engine.
216
+ #
217
+ PATH_CANDIDATES = [
218
+ '/api',
219
+ '/ovirt-engine/api'
220
+ ].freeze
221
+
222
+ def send(opts = {})
223
+ # Get the options and assign default values:
224
+ path = opts[:path] || ''
225
+ version = opts[:version] || '4'
226
+
227
+ # Create the request:
228
+ request = HttpRequest.new
229
+ request.url = "https://#{@host}:#{@port}#{path}"
230
+
231
+ # Set the headers:
232
+ request.headers.merge!(
233
+ 'User-Agent' => "RubyProbe/#{VERSION}",
234
+ 'Version' => version,
235
+ 'Content-Type' => 'application/xml',
236
+ 'Accept' => 'application/xml'
237
+ )
238
+
239
+ # Set authentication:
240
+ request.username = @username
241
+
242
+ request.password = @password
243
+ # Send the request and wait for the response:
244
+ @client.send(request)
245
+ response = @client.wait(request)
246
+ raise response if response.is_a?(Exception)
247
+
248
+ response
249
+ end
250
+
251
+ def detect_path
252
+ PATH_CANDIDATES.each do |path|
253
+ response = send(path: path)
254
+ return path if response.code == 200
255
+ raise AuthError, 'Unauthorized' if response.code == 401
256
+ end
257
+ nil
258
+ end
259
+
260
+ def detect_version(path)
261
+ versions = []
262
+ versions << '3' if detect_v3(path)
263
+ versions << '4' if detect_v4(path)
264
+ versions
265
+ end
266
+
267
+ def detect_v3(path)
268
+ response = send(version: '3', path: path)
269
+ special_response_regexp_in_api3 =~ response.body
270
+ end
271
+
272
+ def detect_v4(path)
273
+ response = send(version: '4', path: path)
274
+ special_response_regexp_in_api4 =~ response.body
275
+ end
276
+
277
+ # The methods below are based on headers oVirt returns depending on the API
278
+ # version it supports when queried with <version: 3> or <version: 4> header.
279
+ # Refer to spec to see the return values.
280
+ def special_response_regexp_in_api3
281
+ /major=/
282
+ end
283
+
284
+ def special_response_regexp_in_api4
285
+ /<major>/
286
+ end
287
+ end
288
+
289
+ #
290
+ # The probe returns an array of instances of this class.
291
+ #
292
+ class ProbeResult
293
+ attr_reader :version
294
+
295
+ #
296
+ # This method is used to initialize a class instance,
297
+ #
298
+ # @param opts [Hash] The attributes of the result.
299
+ #
300
+ # @option opts [String] :version The version obtained as the result of the probe.
301
+ #
302
+ def initialize(opts)
303
+ @version = opts[:version]
304
+ end
305
+
306
+ # Override the comparison method.
307
+ def ==(other)
308
+ other.class == self.class && other.state == state
309
+ end
310
+
311
+ alias eql? ==
312
+
313
+ # Should always be overriden if one overrides ==, used to get a hash value
314
+ # for the object.
315
+ def hash
316
+ state.hash
317
+ end
318
+
319
+ # @api private
320
+ def state
321
+ [version]
322
+ end
323
+ end
324
+ end
@@ -15,7 +15,6 @@
15
15
  #
16
16
 
17
17
  module OvirtSDK4
18
-
19
18
  #
20
19
  # This is the base class for all the XML readers used by the SDK. It contains the utility methods used by all
21
20
  # of them.
@@ -23,7 +22,6 @@ module OvirtSDK4
23
22
  # @api private
24
23
  #
25
24
  class Reader
26
-
27
25
  #
28
26
  # Reads a string value, assuming that the cursor is positioned at the start element that contains the value.
29
27
  #
@@ -31,7 +29,7 @@ module OvirtSDK4
31
29
  # @return [String]
32
30
  #
33
31
  def self.read_string(reader)
34
- return reader.read_element
32
+ reader.read_element
35
33
  end
36
34
 
37
35
  #
@@ -42,7 +40,7 @@ module OvirtSDK4
42
40
  # @return [Array<String>]
43
41
  #
44
42
  def self.read_strings(reader)
45
- return reader.read_elements
43
+ reader.read_elements
46
44
  end
47
45
 
48
46
  #
@@ -53,13 +51,14 @@ module OvirtSDK4
53
51
  #
54
52
  def self.parse_boolean(text)
55
53
  return nil if text.nil?
54
+
56
55
  case text.downcase
57
56
  when 'false', '0'
58
- return false
57
+ false
59
58
  when 'true', '1'
60
- return true
59
+ true
61
60
  else
62
- raise Error.new("The text '#{text}' isn't a valid boolean value.")
61
+ raise Error, "The text '#{text}' isn't a valid boolean value."
63
62
  end
64
63
  end
65
64
 
@@ -70,7 +69,7 @@ module OvirtSDK4
70
69
  # @return [Boolean]
71
70
  #
72
71
  def self.read_boolean(reader)
73
- return Reader.parse_boolean(reader.read_element)
72
+ Reader.parse_boolean(reader.read_element)
74
73
  end
75
74
 
76
75
  #
@@ -81,7 +80,7 @@ module OvirtSDK4
81
80
  # @return [Array<Boolean>]
82
81
  #
83
82
  def self.read_booleans(reader)
84
- return reader.read_elements.map { |text| Reader.parse_boolean(text) }
83
+ reader.read_elements.map { |text| Reader.parse_boolean(text) }
85
84
  end
86
85
 
87
86
  #
@@ -92,10 +91,11 @@ module OvirtSDK4
92
91
  #
93
92
  def self.parse_integer(text)
94
93
  return nil if text.nil?
94
+
95
95
  begin
96
- return Integer(text, 10)
97
- rescue
98
- raise Error.new("The text '#{text}' isn't a valid integer value.")
96
+ Integer(text, 10)
97
+ rescue ArgumentError
98
+ raise Error, "The text '#{text}' isn't a valid integer value."
99
99
  end
100
100
  end
101
101
 
@@ -106,7 +106,7 @@ module OvirtSDK4
106
106
  # @return [Integer]
107
107
  #
108
108
  def self.read_integer(reader)
109
- return Reader.parse_integer(reader.read_element)
109
+ Reader.parse_integer(reader.read_element)
110
110
  end
111
111
 
112
112
  #
@@ -117,20 +117,21 @@ module OvirtSDK4
117
117
  # @return [Array<Integer>]
118
118
  #
119
119
  def self.read_integers(reader)
120
- return reader.read_elements.map { |text| Reader.parse_integer(text) }
120
+ reader.read_elements.map { |text| Reader.parse_integer(text) }
121
121
  end
122
122
 
123
123
  #
124
124
  # Converts the given text to a decimal value.
125
125
  #
126
- # @return [Fixnum]
126
+ # @return [Float]
127
127
  #
128
128
  def self.parse_decimal(text)
129
129
  return nil if text.nil?
130
+
130
131
  begin
131
- return Float(text)
132
- rescue
133
- raise Error.new("The text '#{text}' isn't a valid decimal value.")
132
+ Float(text)
133
+ rescue ArgumentError
134
+ raise Error, "The text '#{text}' isn't a valid decimal value."
134
135
  end
135
136
  end
136
137
 
@@ -138,10 +139,10 @@ module OvirtSDK4
138
139
  # Reads a decimal value, assuming that the cursor is positioned at the start element that contains the value.
139
140
  #
140
141
  # @param reader [XmlReader]
141
- # @return [Fixnum]
142
+ # @return [Float]
142
143
  #
143
144
  def self.read_decimal(reader)
144
- return Reader.parse_decimal(reader.read_element)
145
+ Reader.parse_decimal(reader.read_element)
145
146
  end
146
147
 
147
148
  #
@@ -149,10 +150,10 @@ module OvirtSDK4
149
150
  # values.
150
151
  #
151
152
  # @param reader [XmlReader]
152
- # @return [Array<Fixnum>]
153
+ # @return [Array<Float>]
153
154
  #
154
155
  def self.read_decimals(reader)
155
- return reader.read_elements.map { |text| Reader.parse_decimal(text) }
156
+ reader.read_elements.map { |text| Reader.parse_decimal(text) }
156
157
  end
157
158
 
158
159
  #
@@ -163,10 +164,11 @@ module OvirtSDK4
163
164
  #
164
165
  def self.parse_date(text)
165
166
  return nil if text.nil?
167
+
166
168
  begin
167
- return DateTime.xmlschema(text)
168
- rescue
169
- raise Error.new("The text '#{text}' isn't a valid date.")
169
+ DateTime.xmlschema(text)
170
+ rescue ArgumentError
171
+ raise Error, "The text '#{text}' isn't a valid date."
170
172
  end
171
173
  end
172
174
 
@@ -177,7 +179,7 @@ module OvirtSDK4
177
179
  # @return [DateTime]
178
180
  #
179
181
  def self.read_date(reader)
180
- return Reader.parse_date(reader.read_element)
182
+ Reader.parse_date(reader.read_element)
181
183
  end
182
184
 
183
185
  #
@@ -188,7 +190,45 @@ module OvirtSDK4
188
190
  # @return [Array<DateTime>]
189
191
  #
190
192
  def self.read_dates(reader)
191
- return reader.read_elements.map { |text| Reader.parse_date(text) }
193
+ reader.read_elements.map { |text| Reader.parse_date(text) }
194
+ end
195
+
196
+ #
197
+ # Converts the given text to an enum.
198
+ #
199
+ # @param enum_module [Module]
200
+ # @param text [String]
201
+ # @return [String]
202
+ #
203
+ def self.parse_enum(enum_module, text)
204
+ return nil unless text
205
+
206
+ values = enum_module.constants.map { |const| enum_module.const_get(const) }
207
+ values.detect { |value| value.casecmp(text).zero? }
208
+ end
209
+
210
+ #
211
+ # Reads a enum value, assuming that the cursor is positioned at the
212
+ # start element that contains the value.
213
+ #
214
+ # @param enum_module [Module]
215
+ # @param reader [XmlReader]
216
+ # @return [Array<String>]
217
+ #
218
+ def self.read_enum(enum_module, reader)
219
+ Reader.parse_enum(enum_module, reader.read_element)
220
+ end
221
+
222
+ #
223
+ # Reads a list of enum values, assuming that the cursor is positioned
224
+ # at the start element of the element that contains the first value.
225
+ #
226
+ # @param enum_module [Module]
227
+ # @param reader [XmlReader]
228
+ # @return [Array<String>]
229
+ #
230
+ def self.read_enums(enum_module, reader)
231
+ reader.read_elements.map { |text| Reader.parse_enum(enum_module, text) }
192
232
  end
193
233
 
194
234
  #
@@ -196,7 +236,7 @@ module OvirtSDK4
196
236
  # example, for the `vm` tag it will contain a reference to the `VmReader.read_one` method, and for the `vms` tag
197
237
  # it will contain a reference to the `VmReader.read_many` method.
198
238
  #
199
- @@readers = {}
239
+ @readers = {}
200
240
 
201
241
  #
202
242
  # Registers a read method.
@@ -205,7 +245,7 @@ module OvirtSDK4
205
245
  # @param reader [Method] The reference to the method that reads the object corresponding to the `tag`.
206
246
  #
207
247
  def self.register(tag, reader)
208
- @@readers[tag] = reader
248
+ @readers[tag] = reader
209
249
  end
210
250
 
211
251
  #
@@ -223,7 +263,7 @@ module OvirtSDK4
223
263
  elsif source.is_a?(XmlReader)
224
264
  cursor = source
225
265
  else
226
- raise ArgumentError.new("Expected a 'String' or 'XmlReader', but got '#{source.class}'")
266
+ raise ArgumentError, "Expected a 'String' or 'XmlReader', but got '#{source.class}'"
227
267
  end
228
268
 
229
269
  # Do the actual read, and make sure to always close the XML reader if we created it:
@@ -233,20 +273,14 @@ module OvirtSDK4
233
273
 
234
274
  # Select the specific reader according to the tag:
235
275
  tag = cursor.node_name
236
- reader = @@readers[tag]
237
- if reader.nil?
238
- raise Error.new("Can't find a reader for tag '#{tag}'")
239
- end
276
+ reader = @readers[tag]
277
+ raise Error, "Can't find a reader for tag '#{tag}'" if reader.nil?
240
278
 
241
279
  # Read the object using the specific reader:
242
- return reader.call(cursor)
280
+ reader.call(cursor)
243
281
  ensure
244
- if !cursor.nil? && !cursor.equal?(source)
245
- cursor.close
246
- end
282
+ cursor.close if !cursor.nil? && !cursor.equal?(source)
247
283
  end
248
284
  end
249
-
250
285
  end
251
-
252
286
  end