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.
@@ -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