knife-xapi 0.4.3 → 0.4.6

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.
@@ -21,10 +21,19 @@
21
21
  # See the License for the specific language governing permissions and
22
22
  # limitations under the License.
23
23
 
24
+ unless Kernel.respond_to?(:require_relative)
25
+ module Kernel
26
+ def require_relative(path)
27
+ require File.join(File.dirname(caller[0]), path.to_str)
28
+ end
29
+ end
30
+ end
31
+
24
32
 
25
33
  require 'chef/knife'
26
34
  require 'units/standard'
27
- require 'xenapi'
35
+
36
+ require_relative '../../xenapi/xenapi.rb'
28
37
 
29
38
  class Chef::Knife
30
39
  module XapiBase
@@ -35,7 +44,7 @@ class Chef::Knife
35
44
  includer.class_eval do
36
45
 
37
46
  deps do
38
- require 'xenapi'
47
+ require_relative '../../xenapi/xenapi.rb'
39
48
  require 'highline'
40
49
  require 'highline/import'
41
50
  require 'readline'
@@ -70,6 +79,11 @@ class Chef::Knife
70
79
  :default => false,
71
80
  :description => "Don't colorize the output"
72
81
 
82
+ option :xapi_ssl_verify,
83
+ :long => "--xapi-ssl-verify",
84
+ :default => false,
85
+ :description => "Enable SSL cert verification, Disabled by defaul because xenservers don't ship with proper certs"
86
+
73
87
  end
74
88
  end
75
89
 
@@ -100,7 +114,9 @@ class Chef::Knife
100
114
  @xapi ||= begin
101
115
 
102
116
  ui.fatal "Must provide a xapi host with --host "unless locate_config_value(:xapi_host)
103
- session = XenApi::Client.new( locate_config_value(:xapi_host) )
117
+ verify = :verify_none
118
+ verify = :verify_peer if locate_config_value(:xapi_ssl_verify) == true
119
+ session = XenApi::Client.new( locate_config_value(:xapi_host), 10, verify )
104
120
 
105
121
  # get the password from the user
106
122
  password = locate_config_value(:xapi_password) || nil
@@ -375,11 +391,11 @@ class Chef::Knife
375
391
  #detach_vdi
376
392
  def detach_vdi(vdi_ref)
377
393
  vbd_refs = xapi.VDI.get_VBDs(vdi_ref)
378
-
394
+
379
395
  # more than one VBD, so we ned to find the vbd with the vdi
380
396
  ref = nil
381
397
  Chef::Log.debug "VBD Refs: #{vbd_refs.inspect}"
382
- if vbd_refs.length > 1
398
+ if vbd_refs.length > 1
383
399
  vbd_refs.each do |vbd|
384
400
  record = xapi.VBD.get_record vbd
385
401
  Chef::Log.debug "Checking VBD: #{vbd}, #{record["device"]}, #{record["VDI"]}==#{vdi_ref}"
@@ -388,7 +404,7 @@ class Chef::Knife
388
404
  break
389
405
  end
390
406
  end
391
- else
407
+ else
392
408
  # if only vbd use it
393
409
  ref = vbd_refs.first
394
410
  end
@@ -396,7 +412,7 @@ class Chef::Knife
396
412
  unless ref
397
413
  raise ArgumentError, "We weren't able to find a VBD for that VDI: #{vdi_ref}"
398
414
  end
399
-
415
+
400
416
  task = xapi.Async.VBD.destroy(ref)
401
417
  ui.msg "Waiting for VDI detach"
402
418
  task_ref = get_task_ref(task)
@@ -405,7 +421,7 @@ class Chef::Knife
405
421
  def get_vbd_by_uuid(id)
406
422
  xapi.VBD.get_by_uuid(id)
407
423
  end
408
-
424
+
409
425
  # try to get a guest ip and return it
410
426
  def get_guest_ip(vm_ref)
411
427
  guest_ip = "unknown"
@@ -440,7 +456,7 @@ class Chef::Knife
440
456
  color = [ :clear, :clear ]
441
457
  end
442
458
  ui.msg "#{h.color( key, color[0])} #{ h.color( value, color[1])}"
443
- end
459
+ end
444
460
 
445
461
  def print_vdi_info(vdi)
446
462
  record = xapi.VDI.get_record vdi
@@ -456,19 +472,19 @@ class Chef::Knife
456
472
  color_kv " VM state: ", "#{xapi.VM.get_power_state(vm) } \n"
457
473
  end
458
474
 
459
- if record["VBDs"].length == 0
475
+ if record["VBDs"].length == 0
460
476
  ui.msg h.color " No VM Attached", :red
461
477
  end
462
-
478
+
463
479
  ui.msg ""
464
480
  end
465
481
 
466
- # return true (yes) false (no)
482
+ # return true (yes) false (no)
467
483
  # to the asked question
468
484
  def yes_no?(msg)
469
485
  answer = ui.ask( "#{msg} yes/no ? " ) do |res|
470
486
  res.case = :down
471
- res.validate = /y|n|yes|no/
487
+ res.validate = /y|n|yes|no/
472
488
  res.responses[:not_valid] = "Use 'yes', 'no', 'y', 'n':"
473
489
  end
474
490
 
@@ -482,7 +498,7 @@ class Chef::Knife
482
498
 
483
499
  def destroy_vdi(vdi_ref)
484
500
  vbds = get_vbds_from_vdi(vdi_ref)
485
- unless vbds.empty?
501
+ unless vbds.empty?
486
502
  detach_vdi(vdi_ref)
487
503
  end
488
504
  task = xapi.Async.VDI.destroy(vdi_ref)
@@ -74,6 +74,11 @@ class Chef
74
74
  :description => "You can add more boot options to the vm e.g.: \"ks='http://foo.local/ks'\"",
75
75
  :proc => Proc.new { |key| Chef::Config[:knife][:kernel_params] = key }
76
76
 
77
+ option :xapi_skip_disk,
78
+ :long => "--xapi-skip-disk",
79
+ :proc => Proc.new { |key| Chef::Config[:knife][:xapi_skip_disk] = key },
80
+ :description => "Don't try to add disks to the new VM"
81
+
77
82
  option :xapi_disk_size,
78
83
  :short => "-D Size of disk. 1g 512m etc",
79
84
  :long => "--xapi-disk-size",
@@ -256,6 +261,8 @@ class Chef
256
261
  # if no hostname param set hostname to given vm name
257
262
  boot_args << " hostname=#{server_name}" unless boot_args.match(/hostname=.+\s?/)
258
263
  # if domainname is supplied we put that in there as well
264
+ # ubuntu/debian wants domain rhat wants dnsdomain
265
+ boot_args << " domain=#{domainname}" unless boot_args.match(/domain=.+\s?/)
259
266
  boot_args << " dnsdomain=#{domainname}" unless boot_args.match(/dnsdomain=.+\s?/)
260
267
 
261
268
  xapi.VM.set_PV_args( vm_ref, boot_args )
@@ -270,40 +277,46 @@ class Chef
270
277
  end
271
278
  end
272
279
 
273
- if locate_config_value(:xapi_sr)
274
- sr_ref = get_sr_by_name( locate_config_value(:xapi_sr) )
275
- else
276
- sr_ref = find_default_sr
277
- end
280
+ unless locate_config_value(:xapi_skip_disk)
281
+ sr_ref = nil
282
+ if locate_config_value(:xapi_sr)
283
+ sr_ref = get_sr_by_name( locate_config_value(:xapi_sr) )
284
+ else
285
+ sr_ref = find_default_sr
286
+ end
278
287
 
279
- if sr_ref.nil?
280
- ui.error "SR specified not found or can't be used Aborting"
281
- fail(vm_ref) if sr_ref.nil?
282
- end
283
- Chef::Log.debug "SR: #{h.color sr_ref, :cyan}"
284
-
285
- # setup disks
286
- if locate_config_value(:xapi_disk_size)
287
- # when a template already has disks, we decide the position number based on it.
288
- position = xapi.VM.get_VBDs(vm_ref).length
289
-
290
- # Create the VDI
291
- vdi_ref = create_vdi("#{server_name}-root", sr_ref, locate_config_value(:xapi_disk_size) )
292
- fail(vm_ref) unless vdi_ref
293
-
294
- # Attach the VDI to the VM
295
- # if its position is 0 set it bootable
296
- vbd_ref = create_vbd(vm_ref, vdi_ref, position, position == 0)
297
- fail(vm_ref) unless vbd_ref
298
- end
288
+ if sr_ref.nil?
289
+ ui.error "SR specified not found or can't be used Aborting"
290
+ fail(vm_ref) if sr_ref.nil?
291
+ end
292
+ Chef::Log.debug "SR: #{h.color sr_ref, :cyan}"
293
+
299
294
 
295
+ disk_size = locate_config_value(:xapi_disk_size)
296
+ # setup disks
297
+ if disk_size != nil and disk_size.to_i > 0
298
+ # when a template already has disks, we decide the position number based on it.
299
+ position = xapi.VM.get_VBDs(vm_ref).length
300
+
301
+ # Create the VDI
302
+ vdi_ref = create_vdi("#{server_name}-root", sr_ref, locate_config_value(:xapi_disk_size) )
303
+ fail(vm_ref) unless vdi_ref
304
+
305
+ # Attach the VDI to the VM
306
+ # if its position is 0 set it bootable
307
+ position == 0 ? bootable=true : bootable=false
308
+
309
+ vbd_ref = create_vbd(vm_ref, vdi_ref, position, bootable)
310
+ fail(vm_ref) unless vbd_ref
311
+ end
312
+ end
300
313
 
301
314
  ui.msg "Provisioning new Guest: #{h.color(fqdn, :bold, :cyan )}"
302
315
  ui.msg "Boot Args: #{h.color boot_args,:bold, :cyan}"
303
316
  ui.msg "Install Repo: #{ h.color(repo,:bold, :cyan)}"
304
317
  ui.msg "Memory: #{ h.color( locate_config_value(:xapi_mem).to_s, :bold, :cyan)}"
305
318
  ui.msg "CPUs: #{ h.color( locate_config_value(:xapi_cpus).to_s, :bold, :cyan)}"
306
- ui.msg "Disk: #{ h.color( locate_config_value(:xapi_disk_size).to_s, :bold, :cyan)}"
319
+ ui.msg "Disk: #{ h.color( disk_size.to_s, :bold, :cyan)}"
307
320
  provisioned = xapi.VM.provision(vm_ref)
308
321
 
309
322
  ui.msg "Starting new Guest #{h.color( provisioned, :cyan)} "
@@ -1,3 +1,3 @@
1
1
  module KnifeXenserver
2
- VERSION = "0.4.3"
2
+ VERSION = "0.4.6"
3
3
  end
@@ -0,0 +1,3 @@
1
+ I've decided to pull in xenapi with ssl support instead of using the external gem that still has no ssl verification options
2
+
3
+ if the upstream does implemnt some version of this we can adjust
@@ -0,0 +1 @@
1
+ require 'xenapi'
@@ -0,0 +1,47 @@
1
+ module XenApi
2
+ autoload :Client, File.expand_path('../xenapi/client', __FILE__)
3
+ autoload :Errors, File.expand_path('../xenapi/errors', __FILE__)
4
+ autoload :Dispatcher, File.expand_path('../xenapi/dispatcher', __FILE__)
5
+ autoload :AsyncDispatcher, File.expand_path('../xenapi/async_dispatcher', __FILE__)
6
+ autoload :XMLRPCClient, File.expand_path('../xenapi/xmlrpc_client', __FILE__)
7
+
8
+ # Perform some action in a session context
9
+ #
10
+ # @param [String,Array] hosts
11
+ # Host or hosts to try to connect to. Pass multiple URLs to allow to find
12
+ # the pool master even if the originally designated host is not reachable.
13
+ # @param [String] username Username used for login
14
+ # @param [String] password Password used for login
15
+ # @param [Hash(Symbol => Boolean, String)] options
16
+ # Additional options:
17
+ # +:api_version+:: Force the usage of this API version if true
18
+ # +:slave_login+:: Authenticate locally against a slave in emergency mode if true.
19
+ # +:keep_session+:: Don't logout afterwards to keep the session usable if true
20
+ # +:timeout+:: Maximum number of seconds to wait for an API response
21
+ # +:ssl_verify+:: SSL certificate verification mode. Can be one of :verify_none or :verify_peer
22
+ # @yield client
23
+ # @yieldparam [Client] client Client instance
24
+ # @return [Object] block return value
25
+ # @raise [NoHostsAvailable] No hosts could be contacted
26
+ def self.connect(uris, username, password, options={})
27
+ uris = uris.respond_to?(:shift) ? uris.dup : [uris]
28
+ method = options[:slave_login] ? :slave_local_login_with_password : :login_with_password
29
+
30
+ client = Client.new(uris, options[:timeout] || 10, options[:ssl_verify] || :verify_peer)
31
+ begin
32
+ args = [method, username, password]
33
+ args << options[:api_version] if options.has_key?(:api_version)
34
+ client.send(*args)
35
+
36
+ if block_given?
37
+ return yield client
38
+ else
39
+ options[:keep_session] = true
40
+ return client
41
+ end
42
+ ensure
43
+ client.logout unless options[:keep_session] || client.xenapi_session.nil?
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,36 @@
1
+ module XenApi #:nodoc:
2
+ # @private
3
+ # This class helps to provide the ability for the +XenApi::Client+
4
+ # to accept +async+ method calls. Calls are similar to synchronous
5
+ # method calls except that the names are prefixed with 'Async'.
6
+ #
7
+ # client = XenApi::Client.new('http://xenapi.test/')
8
+ # client.async #=> AsyncDispatcher instance
9
+ # client.async.VM #=> Dispatcher instance for 'Async.VM'
10
+ # client.async.VM.start() #=> Performs XMLRPC 'Async.VM.start' call
11
+ #
12
+ # further calls on instances of this object will create a +Dispatcher+
13
+ # instance which then handle actual method calls.
14
+ class AsyncDispatcher
15
+ # @param [Client] client XenApi::Client instance
16
+ # @param [Symbol] sender XenApi::Client method to call when prefix method is invoked
17
+ def initialize(client, sender)
18
+ @client = client
19
+ @sender = sender
20
+ end
21
+
22
+ # @see Object#inspect
23
+ def inspect
24
+ "#<#{self.class}>"
25
+ end
26
+
27
+ # Create a new +Dispatcher+ instance to handle the +Async.meth+ prefix.
28
+ #
29
+ # @param [String,Symbol] meth Method prefix name
30
+ # @param [...] args Method arguments
31
+ # @return [Dispatcher] dispatcher instance to handle the +Async.meth+ prefix
32
+ def method_missing(meth, *args)
33
+ Dispatcher.new(@client, "Async.#{meth}", @sender)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,382 @@
1
+ require 'uri'
2
+ require 'xmlrpc/client'
3
+
4
+ module XenApi #:nodoc:
5
+ # This class permits the invocation of XMLRPC API calls
6
+ # through a ruby-like interface
7
+ #
8
+ # client = XenApi::Client.new('http://xenapi.test')
9
+ # client.login_with_password('root', 'password')
10
+ # client.VM.get_all
11
+ #
12
+ # == Authenticating with the API
13
+ # Authentication with the API takes place through the API
14
+ # +session+ class, usually using the +login_with_password+
15
+ # method. The +Client+ handles this method specially to
16
+ # enable it to retain the session identifier to pass to
17
+ # invoked methods and perform reauthentication should the
18
+ # session become stale.
19
+ #
20
+ # client = XenApi::Client.new('http://xenapi.test')
21
+ # client.login_with_password('root', 'password')
22
+ #
23
+ # It is worth noting that only +login*+ matching methods
24
+ # are specially passed through to the +session+ class.
25
+ #
26
+ # == Running code after API login
27
+ # The +Client+ provides the ability for running code
28
+ # after the client has successfully authenticated with
29
+ # the API. This is useful for either logging authentication
30
+ # or for registering for certain information from the API.
31
+ #
32
+ # The best example of this is when needing to make use of
33
+ # the Xen API +event+ class for asynchronous event handling.
34
+ # To use the API +event+ class you first have to register
35
+ # your interest in a specific set of event types.
36
+ #
37
+ # client = XenApi::Client.new('http://xenapi.test')
38
+ # client.after_login do |c|
39
+ # c.event.register %w(vm) # register for 'vm' events
40
+ # end
41
+ #
42
+ # == Asynchronous Methods
43
+ # To call asynchronous methods on the Xen XMLRPC API you
44
+ # first call +Async+ on the +Client+ instance followed by
45
+ # the normal method name.
46
+ # For example:
47
+ #
48
+ # client = XenApi::Client.new('http://xenapi.test')
49
+ # client.login_with_password('root', 'password')
50
+ #
51
+ # vm_ref = client.VM.get_by_name_label('my vm')
52
+ # task = client.Async.VM.clone(vm_ref)
53
+ # while client.Task.get_status(task) == "pending":
54
+ # progress = client.Task.get_progress(task)
55
+ # update_progress_bar(progress)
56
+ # time.sleep(1)
57
+ # client.Task.destroy(task)
58
+ #
59
+ # Calling either +Async+ or +async+ will work as the
60
+ # capitalised form will always be sent when calling
61
+ # a method asynchronously.
62
+ #
63
+ # Note that only some methods are available in an asynchronous variant.
64
+ # An XMLRPC::FaultException is thrown if you try to call a method
65
+ # asynchrounously that is not available.
66
+ class Client
67
+ # The +LoginRequired+ exception is raised when
68
+ # an API request requires login and no login
69
+ # credentials have yet been provided.
70
+ #
71
+ # If you don't perform a login before receiving this
72
+ # exception then you will want to catch it, log into
73
+ # the API and then retry your request.
74
+ class LoginRequired < RuntimeError; end
75
+
76
+ # The +SessionInvalid+ exception is raised when the
77
+ # API session has become stale or is otherwise invalid.
78
+ #
79
+ # Internally this exception will be handled a number of
80
+ # times before being raised up to the calling code.
81
+ class SessionInvalid < RuntimeError; end
82
+
83
+ # The +ResponseMissingStatusField+ exception is raised
84
+ # when the XMLRPC response is missing the +Status+ field.
85
+ # This typically indicates an unrecoverable error with
86
+ # the API itself.
87
+ class ResponseMissingStatusField < RuntimeError; end
88
+
89
+ # The +ResponseMissingValueField+ exception is raised
90
+ # when the XMLRPC response is missing the +Value+ field.
91
+ # This typically indicates an unrecoverable error with
92
+ # the API itself.
93
+ class ResponseMissingValueField < RuntimeError; end
94
+
95
+ # The +ResponseMissingErrorDescriptionField+ exception
96
+ # is raised when an error is returned in the XMLRPC
97
+ # response, but the type of error cannot be determined
98
+ # due to the lack of the +ErrorDescription+ field.
99
+ class ResponseMissingErrorDescriptionField < RuntimeError; end
100
+
101
+ # @see Object#inspect
102
+ def inspect
103
+ "#<#{self.class} #{@uri}>"
104
+ end
105
+
106
+ # @param [String,Array] uri URL to the Xen API endpoint
107
+ # @param [Integer] timeout Maximum number of seconds to wait for an API response
108
+ # @param [Symbol] ssl_verify SSL certificate verification mode.
109
+ # Can be one of :verify_none or :verify_peer
110
+ def initialize(uris, timeout=10, ssl_verify=:verify_peer)
111
+ @timeout = timeout
112
+ @ssl_verify = ssl_verify
113
+ @uris = [uris].flatten.collect do |uri|
114
+ uri = URI.parse(uri)
115
+ uri.path = '/' if uri.path == ''
116
+ uri
117
+ end.uniq
118
+ @uri = @uris.first
119
+ end
120
+
121
+ attr_reader :uri, :uris
122
+
123
+ # @overload after_login
124
+ # Adds a block to be called after successful login to the XenAPI.
125
+ # @note The block will be called whenever the receiver has to authenticate
126
+ # with the XenAPI. This includes the first time the receiver recieves a
127
+ # +login_*+ method call and any time the session becomes invalid.
128
+ # @yield client
129
+ # @yieldparam [optional, Client] client Client instance
130
+ # @overload after_login
131
+ # Calls the created block, this is primarily for internal use only
132
+ # @return [Client] receiver
133
+ def after_login(&block)
134
+ if block
135
+ @after_login = block
136
+ elsif @after_login
137
+ case @after_login.arity
138
+ when 1
139
+ @after_login.call(self)
140
+ else
141
+ @after_login.call
142
+ end
143
+ end
144
+ self
145
+ end
146
+
147
+ # @overload before_reconnect
148
+ # Adds a block to be called before an attempted reconnect to another server.
149
+ # @note The block will be called whenever the receiver has to chose a
150
+ # new server because the current connection got invalid.
151
+ # @yield client
152
+ # @yieldparam [optional, Client] client Client instance
153
+ # @overload before_reconnect
154
+ # Calls the created block, this is primarily for internal use only
155
+ # @return [Client] receiver
156
+ def before_reconnect(&block)
157
+ if block
158
+ @before_reconnect = block
159
+ elsif @before_reconnect
160
+ case @before_reconnect.arity
161
+ when 1
162
+ @before_reconnect.call(self)
163
+ else
164
+ @before_reconnect.call
165
+ end
166
+ end
167
+ self
168
+ end
169
+
170
+ # Returns the current session identifier.
171
+ #
172
+ # @return [String] session identifier
173
+ def xenapi_session
174
+ @session
175
+ end
176
+
177
+ # Returns the current API version
178
+ #
179
+ # @return [String] API version
180
+ def api_version
181
+ @api_version ||= begin
182
+ pool = self.pool.get_all()[0]
183
+ host = self.pool.get_master(pool)
184
+ major = self.host.get_API_version_major(host)
185
+ minor = self.host.get_API_version_minor(host)
186
+ "#{major}.#{minor}"
187
+ end
188
+ end
189
+
190
+ # Handle API method calls.
191
+ #
192
+ # If the method called starts with +login+ then the method is
193
+ # assumed to be part of the +session+ namespace and will be
194
+ # called directly. For example +login_with_password+
195
+ #
196
+ # client = XenApi::Client.new('http://xenapi.test/')
197
+ # client.login_with_password('root', 'password)
198
+ #
199
+ # If the method called is +async+ then an +AsyncDispatcher+
200
+ # will be created to handle the asynchronous API method call.
201
+ #
202
+ # client = XenApi::Client.new('http://xenapi.test/')
203
+ # client.async.host.get_servertime(ref)
204
+ #
205
+ # The final case will create a +Dispatcher+ to handle the
206
+ # subsequent method call such as.
207
+ #
208
+ # client = XenApi::Client.new('http://xenapi.test/')
209
+ # client.host.get_servertime(ref)
210
+ #
211
+ # @note +meth+ names are not validated
212
+ #
213
+ # @param [String,Symbol] meth Method name
214
+ # @param [...] args Method args
215
+ # @return [true,AsyncDispatcher,Dispatcher]
216
+ def method_missing(meth, *args)
217
+ case meth.to_s
218
+ when /^(slave_local_)?login/
219
+ _login(meth, *args)
220
+ when /^async/i
221
+ AsyncDispatcher.new(self, :_call)
222
+ else
223
+ Dispatcher.new(self, meth, :_call)
224
+ end
225
+ end
226
+
227
+ # Logout and destroy the current session. After calling logout, the
228
+ # object state is invalid. No API calls can be performed unless one of
229
+ # the login methods is called again.
230
+ def logout
231
+ begin
232
+ if @login_meth.to_s.start_with? "slave_local"
233
+ _do_call("session.local_logout", [@session])
234
+ else
235
+ _do_call("session.logout", [@session])
236
+ end
237
+ rescue
238
+ # We don't care about any error. If it works: great, if not: shit happens...
239
+ nil
240
+ ensure
241
+ @session = ""
242
+ @login_meth = nil
243
+ @login_args = []
244
+ @api_version = nil
245
+ end
246
+ end
247
+
248
+ protected
249
+ # @param [String,Symbol] meth API method to call
250
+ # @param [Array] args Arguments to pass to the method call
251
+ # @raise [SessionInvalid] Reauthentication failed
252
+ # @raise [LoginRequired] Authentication required, unable to login automatically
253
+ # @raise [EOFError] XMLRPC::Client exception
254
+ # @raise [Errno::EPIPE] XMLRPC::Client exception
255
+ def _call(meth, *args)
256
+ begin
257
+ _do_call(meth, args.dup.unshift(@session))
258
+ rescue SessionInvalid
259
+ _relogin_attempts = (_relogin_attempts || 0) + 1
260
+ _relogin
261
+ retry unless _relogin_attempts > 2
262
+ _reconnect ? retry : raise
263
+ rescue Timeout::Error
264
+ _timeout_retries = (_timeout_retries || 0) + 1
265
+ @client = nil
266
+ retry unless _timeout_retries > 1
267
+ _reconnect ? retry : raise
268
+ rescue EOFError
269
+ _eof_retries = (_eof_retries || 0) + 1
270
+ @client = nil
271
+ retry unless _eof_retries > 1
272
+ _reconnect ? retry : raise
273
+ rescue Errno::EPIPE
274
+ _epipe_retries = (_epipe_retries || 0) + 1
275
+ @client = nil
276
+ retry unless _epipe_retries > 1
277
+ _reconnect ? retry : raise
278
+ rescue Errno::EHOSTUNREACH
279
+ @client = nil
280
+ _reconnect ? retry : raise
281
+ end
282
+ end
283
+
284
+ private
285
+ # Reauthenticate with the API
286
+ # @raise [LoginRequired] Missing authentication credentials
287
+ def _relogin
288
+ raise LoginRequired if @login_meth.nil? || @login_args.nil? || @login_args.empty?
289
+ _login(@login_meth, *@login_args)
290
+ end
291
+
292
+ # Try to reconnect to another available server in the same pool
293
+ #
294
+ # @note Will call the +before_reconnect+ block before trying to reconnect
295
+ #
296
+ # @raise [Errors::NoHostsAvailable] No further hosts available to connect to
297
+ # @raise [LoginRequired] Missing authentication credentials
298
+ def _reconnect
299
+ return false if @i_am_reconnecting
300
+
301
+ @i_am_reconnecting = true
302
+ failed_uris = [@uri]
303
+ while (available_uris = (@uris - failed_uris)).count > 0
304
+ @uri = available_uris[0]
305
+ @client = nil
306
+
307
+ begin
308
+ before_reconnect
309
+ _relogin
310
+ @i_am_reconnecting = false
311
+ return true
312
+ rescue LoginRequired
313
+ raise
314
+ rescue
315
+ failed_uris << @uri
316
+ end
317
+ end
318
+ raise Errors::NoHostsAvailable.new("No server reachable. Giving up.")
319
+ end
320
+
321
+ # Login to the API
322
+ #
323
+ # @note Will call the +after_login+ block if login is successful
324
+ #
325
+ # @param [String,Symbol] meth Login method name
326
+ # @param [...] args Arguments to pass to the login method
327
+ # @return [Boolean] true
328
+ # @raise [Exception] any exception raised by +_do_call+ or +after_login+
329
+ def _login(meth, *args)
330
+ begin
331
+ @session = _do_call("session.#{meth}", args)
332
+ rescue Errors::HostIsSlave => e
333
+ @uri = @uri.dup
334
+ @uri.host = e.description[0]
335
+ @uris.unshift(@uri).uniq!
336
+ @client = nil
337
+ retry
338
+ end
339
+
340
+ @login_meth = meth
341
+ @login_args = args
342
+ after_login
343
+ true
344
+ end
345
+
346
+ # Return or initialize new +XMLRPC::Client+
347
+ #
348
+ # @return [XMLRPC::Client] XMLRPC client instance
349
+ def _client
350
+ @client ||= XMLRPCClient.new(@uri.host, @uri.path, @uri.port, nil, nil, nil, nil, @uri.scheme == "https" ? @ssl_verify : false, @timeout)
351
+ end
352
+
353
+ # Perform XMLRPC method call.
354
+ #
355
+ # @param [String,Symbol] meth XMLRPC method to call
356
+ # @param [Array] args XMLRPC method arguments
357
+ # @param [Integer] attempts Number of times to retry the call, presently unused
358
+ # @return [Object] method return value
359
+ # @raise [ResponseMissingStatusField] XMLRPC response does not have a +Status+ field
360
+ # @raise [ResponseMissingValueField] XMLRPC response does not have a +Value+ field
361
+ # @raise [ResponseMissingErrorDescriptionField] API response error missing +ErrorDescription+ field
362
+ # @raise [SessionInvalid] API session has expired
363
+ # @raise [Errors::GenericError] API method specific error
364
+ def _do_call(meth, args, attempts = 3)
365
+ r = _client.call(meth, *args)
366
+ raise ResponseMissingStatusField unless r.has_key?('Status')
367
+
368
+ if r['Status'] == 'Success'
369
+ return r['Value'] if r.has_key?('Value')
370
+ raise ResponseMissingValueField
371
+ else
372
+ raise ResponseMissingErrorDescriptionField unless r.has_key?('ErrorDescription')
373
+ raise SessionInvalid if r['ErrorDescription'][0] == 'SESSION_INVALID'
374
+
375
+ ed = r['ErrorDescription'].shift
376
+ ex = Errors.exception_class_from_desc(ed)
377
+ r['ErrorDescription'].unshift(ed) if ex == Errors::GenericError
378
+ raise ex, r['ErrorDescription']
379
+ end
380
+ end
381
+ end
382
+ end
@@ -0,0 +1,48 @@
1
+ module XenApi #:nodoc:
2
+ # @private
3
+ # This class helps to provide XMLRPC method dispatching.
4
+ #
5
+ # Calls made to the top level +XenApi::Client+ instance
6
+ # will generate instances of this class to provide scoping
7
+ # of methods by their prefix. All Xen API method calls are
8
+ # two level, the first level specifies a namespace or prefix
9
+ # for the second level method call. Taking +VM.start+ as
10
+ # an example, +VM+ is the namespace prefix and +start+ is
11
+ # the method name.
12
+ #
13
+ # Calling Xen API XMLRPC methods therefore consists of
14
+ # first creating a +Dispatcher+ instance with the prefix
15
+ # name and then calling a method on the +Dispatcher+
16
+ # instance to create the XMLRPC method name to be called
17
+ # by the +XenApi::Client+ instance.
18
+ #
19
+ # client = XenApi::Client.new('http://xenapi.test/')
20
+ # client.VM #=> Dispatcher instance for 'VM'
21
+ # client.VM.start() #=> Performs XMLRPC 'VM.start' call
22
+ class Dispatcher
23
+ undef :clone # to allow for VM.clone calls
24
+
25
+ # @param [Client] client XenApi::Client instance
26
+ # @param [String] prefix Method prefix name
27
+ # @param [Symbol] sender XenApi::Client method to call when prefix method is invoked
28
+ def initialize(client, prefix, sender)
29
+ @client = client
30
+ @prefix = prefix
31
+ @sender = sender
32
+ end
33
+
34
+ # @see Object#inspect
35
+ def inspect
36
+ "#<#{self.class} #{@prefix}>"
37
+ end
38
+
39
+ # Calls a method on +XenApi::Client+ to perform the XMLRPC method
40
+ #
41
+ # @param [String,Symbol] meth Method name to be combined with the receivers +prefix+
42
+ # @param [...] args Method arguments
43
+ # @return [Object] XMLRPC response value
44
+ def method_missing(meth, *args)
45
+ @client.send(@sender, "#{@prefix}.#{meth}", *args)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,469 @@
1
+ module XenApi #:nodoc:
2
+ module Errors #:nodoc:
3
+ # Generic errror case, all XenApi exceptions inherit from this for ease of catching
4
+ class GenericError < RuntimeError;
5
+ # The raw error description according to the server, typically an array
6
+ attr_reader :description
7
+
8
+ def initialize(*args, &block)
9
+ @description = args[0]
10
+ args[0] = args[0].inspect unless args[0].is_a? String || args[0].nil?
11
+ super(*args, &block)
12
+ end
13
+ end
14
+
15
+ # The bootloader returned an error.
16
+ #
17
+ # Raised by
18
+ # - VM.start
19
+ # - VM.start_on
20
+ class BootloaderFailed < GenericError; end
21
+
22
+ # The device is not currently attached
23
+ #
24
+ # Raised by
25
+ # - VBD.unplug
26
+ class DeviceAlreadyDetached < GenericError; end
27
+
28
+ # The VM rejected the attempt to detach the device.
29
+ #
30
+ # Raised by
31
+ # - VBD.unplug
32
+ class DeviceDetachRejected < GenericError; end
33
+
34
+ # Some events have been lost from the queue and cannot be retrieved.
35
+ #
36
+ # Raised by
37
+ # - event.next
38
+ class EventsLost < GenericError; end
39
+
40
+ # This operation cannot be performed because it would invalidate VM
41
+ # failover planning such that the system would be unable to guarantee
42
+ # to restart protected VMs after a Host failure.
43
+ #
44
+ # Raised by
45
+ # - VM.set_memory_static_max
46
+ class HAOperationWouldBreakFailoverPlan < GenericError; end
47
+
48
+ # The host can not be used as it is not the pool master. The data
49
+ # contains the location of the current designated pool master.
50
+ #
51
+ # Raised by
52
+ # - sesssion.login_with_password
53
+ class HostIsSlave < GenericError; end
54
+
55
+ # The host name is invalid
56
+ #
57
+ # Raised by
58
+ # - host.set_hostname_live
59
+ class HostNameInvalid < GenericError; end
60
+
61
+ # Not enough host memory is available to perform this operation
62
+ #
63
+ # Raised by
64
+ # - VM.assert_can_boot_here
65
+ class HostNotEnoughFreeMemory < GenericError; end
66
+
67
+ # You tried to create a VLAN or tunnel on top of a tunnel access
68
+ # PIF - use the underlying transport PIF instead.
69
+ #
70
+ # Raised by
71
+ # - tunnel.create
72
+ class IsTunnelAccessPIF < GenericError; end
73
+
74
+ # The host joining the pool cannot contain any shared storage.
75
+ #
76
+ # Raised by
77
+ # - pool.join
78
+ class JoiningHostCannotContainSharedSRs < GenericError; end
79
+
80
+ # This operation is not allowed under your license.
81
+ # Please contact your support representative.
82
+ #
83
+ # Raised by
84
+ # - VM.start
85
+ class LicenceRestriction < GenericError; end
86
+
87
+ # There was an error processing your license. Please contact
88
+ # your support representative.
89
+ #
90
+ # Raised by
91
+ # - host.license_apply
92
+ class LicenseProcessingError < GenericError; end
93
+
94
+ # There were no hosts available to complete the specified operation.
95
+ #
96
+ # Raised by
97
+ # - VM.start
98
+ class NoHostsAvailable < GenericError; end
99
+
100
+ # This operation needs the OpenVSwitch networking backend to be
101
+ # enabled on all hosts in the pool.
102
+ #
103
+ # Raised by
104
+ # - tunnel.create
105
+ class OpenvswitchNotActive < GenericError; end
106
+
107
+ # You attempted an operation that was not allowed.
108
+ #
109
+ # Raised by
110
+ # - task.cancel
111
+ # - VM.checkpoint
112
+ # - VM.clean_reboot
113
+ # - VM.clean_shutdown
114
+ # - VM.clone
115
+ # - VM.copy
116
+ # - VM.hard_reboot
117
+ # - VM.hard_shutdown
118
+ # - VM.pause
119
+ # - VM.pool_migrate
120
+ # - VM.provision
121
+ # - VM.resume
122
+ # - VM.resume_on
123
+ # - VM.revert
124
+ # - VM.snapshot
125
+ # - VM.snapshot_with_quiesce
126
+ # - VM.start
127
+ # - VM.start_on
128
+ # - VM.suspend
129
+ # - VM.unpause
130
+ class OperationNotAllowed < GenericError; end
131
+
132
+ # Another operation involving the object is currently in progress
133
+ #
134
+ # Raised by
135
+ # - VM.clean_reboot
136
+ # - VM.clean_shutdown
137
+ # - VM.hard_reboot
138
+ # - VM.hard_shutdown
139
+ # - VM.pause
140
+ # - VM.pool_migrate
141
+ # - VM.start
142
+ # - VM.start_on
143
+ # - VM.suspend
144
+ class OtherOperationInProgress < GenericError; end
145
+
146
+ # You tried to destroy a PIF, but it represents an aspect of the physical
147
+ # host configuration, and so cannot be destroyed. The parameter echoes the
148
+ # PIF handle you gave.
149
+ #
150
+ # Raised by
151
+ # - PIF.destroy
152
+ # @deprecated the PIF.destroy method is deprecated in XenServer 4.1 and replaced
153
+ # by VLAN.destroy and Bond.destroy
154
+ class PIFIsPhysical < GenericError; end
155
+
156
+ # Operation cannot proceed while a tunnel exists on this interface.
157
+ #
158
+ # Raised by
159
+ # - PIF.forget
160
+ class PIFTunnelStillExists < GenericError; end
161
+
162
+ # The credentials given by the user are incorrect, so access has been
163
+ # denied, and you have not been issued a session handle.
164
+ #
165
+ # Raised by
166
+ # - session.login_with_password
167
+ class SessionAuthenticationFailed < GenericError; end
168
+
169
+ # This session is not registered to receive events. You must call
170
+ # event.register before event.next. The session handle you are
171
+ # using is echoed.
172
+ #
173
+ # Raised by
174
+ # - event.next
175
+ class SessionNotRegistered < GenericError; end
176
+
177
+ # The SR is full. Requested new size exceeds the maximum size
178
+ #
179
+ # Raised by
180
+ # - VM.checkpoint
181
+ # - VM.clone
182
+ # - VM.copy
183
+ # - VM.provision
184
+ # - VM.revert
185
+ # - VM.snapshot
186
+ # - VM.snapshot_with_quiesce
187
+ class SRFull < GenericError; end
188
+
189
+ # The SR is still connected to a host via a PBD. It cannot be destroyed.
190
+ #
191
+ # Raised by
192
+ # - SR.destroy
193
+ # - SR.forget
194
+ class SRHasPDB < GenericError; end
195
+
196
+ # The SR backend does not support the operation (check the SR's allowed operations)
197
+ #
198
+ # Raised by
199
+ # - VDI.introduce
200
+ # - VDI.update
201
+ class SROperationNotSupported < GenericError; end
202
+
203
+ # The SR could not be connected because the driver was not recognised.
204
+ #
205
+ # Raised by
206
+ # - PBD.plug
207
+ # - SR.create
208
+ class SRUnknownDriver < GenericError; end
209
+
210
+ # The tunnel transport PIF has no IP configuration set.
211
+ #
212
+ # Raised by
213
+ # - PIF.plug
214
+ # - tunnel.create
215
+ class TransportPIFNotConfigured < GenericError; end
216
+
217
+ # The requested bootloader is unknown
218
+ #
219
+ # Raised by
220
+ # - VM.start
221
+ # - VM.start_on
222
+ class UnknownBootloader < GenericError; end
223
+
224
+ # Operation could not be performed because the drive is empty
225
+ #
226
+ # Raised by
227
+ # - VBD.eject
228
+ class VBDIsEmpty < GenericError; end
229
+
230
+ # Operation could not be performed because the drive is not empty
231
+ #
232
+ # Raised by
233
+ # - VBD.insert
234
+ class VBDNotEmpty < GenericError; end
235
+
236
+ # Media could not be ejected because it is not removable
237
+ #
238
+ # Raised by
239
+ # - VBD.eject
240
+ # - VBD.insert
241
+ class VBDNotRemovableMedia < GenericError; end
242
+
243
+ # You tried to create a VLAN, but the tag you gave was invalid --
244
+ # it must be between 0 and 4094. The parameter echoes the VLAN tag you gave.
245
+ #
246
+ # Raised by
247
+ # - PIF.create_VLAN (deprecated)
248
+ # - pool.create_VLAN (deprecated)
249
+ # - pool.create_VLAN_from_PIF
250
+ class VlanTagInvalid < GenericError; end
251
+
252
+ # You attempted an operation on a VM that was not in an appropriate
253
+ # power state at the time; for example, you attempted to start a VM
254
+ # that was already running. The parameters returned are the VM's
255
+ # handle, and the expected and actual VM state at the time of the call.
256
+ #
257
+ # Raised by
258
+ # - VM.checkpoint
259
+ # - VM.clean_reboot
260
+ # - VM.clean_shutdown
261
+ # - VM.checkpoint
262
+ # - VM.clean_reboot
263
+ # - VM.clean_shutdown
264
+ # - VM.clone
265
+ # - VM.copy
266
+ # - VM.hard_reboot
267
+ # - VM.hard_shutdown
268
+ # - VM.pause
269
+ # - VM.pool_migrate
270
+ # - VM.provision
271
+ # - VM.resume
272
+ # - VM.resume_on
273
+ # - VM.revert
274
+ # - VM.send_sysrq
275
+ # - VM.send_trigger
276
+ # - VM.snapshot
277
+ # - VM.snapshot_with_quiesce
278
+ # - VM.start
279
+ # - VM.start_on
280
+ # - VM.suspend
281
+ # - VM.unpause
282
+ class VMBadPowerState < GenericError; end
283
+
284
+ # An error occured while restoring the memory image of the
285
+ # specified virtual machine
286
+ #
287
+ # Raised by
288
+ # - VM.checkpoint
289
+ class VMCheckpointResumeFailed < GenericError; end
290
+
291
+ # An error occured while saving the memory image of the
292
+ # specified virtual machine
293
+ #
294
+ # Raised by
295
+ # - VM.checkpoint
296
+ class VMCheckpointSuspendFailed < GenericError; end
297
+
298
+ # HVM is required for this operation
299
+ #
300
+ # Raised by
301
+ # - VM.start
302
+ class VMHvmRequired < GenericError; end
303
+
304
+ # The operation attempted is not valid for a template VM
305
+ #
306
+ # Raised by
307
+ # - VM.clean_reboot
308
+ # - VM.clean_shutdown
309
+ # - VM.hard_reboot
310
+ # - VM.hard_shutdown
311
+ # - VM.pause
312
+ # - VM.pool_migrate
313
+ # - VM.resume
314
+ # - VM.resume_on
315
+ # - VM.start
316
+ # - VM.start_on
317
+ # - VM.suspend
318
+ # - VM.unpause
319
+ class VMIsTemplate < GenericError; end
320
+
321
+ # An error occurred during the migration process.
322
+ #
323
+ # Raised by
324
+ # - VM.pool_migrate
325
+ class VMMigrateFailed < GenericError; end
326
+
327
+ # You attempted an operation on a VM which requires PV drivers
328
+ # to be installed but the drivers were not detected.
329
+ #
330
+ # Raised by
331
+ # -VM.pool_migrate
332
+ class VMMissingPVDrivers < GenericError; end
333
+
334
+ # You attempted to run a VM on a host which doesn't have access to an SR
335
+ # needed by the VM. The VM has at least one VBD attached to a VDI in the SR
336
+ #
337
+ # Raised by
338
+ # - VM.assert_can_boot_here
339
+ class VMRequiresSR < GenericError; end
340
+
341
+ # An error occured while reverting the specified virtual machine
342
+ # to the specified snapshot
343
+ #
344
+ # Raised by
345
+ # - VM.revert
346
+ class VMRevertFailed < GenericError; end
347
+
348
+ # The quiesced-snapshot operation failed for an unexpected reason
349
+ #
350
+ # Raised by
351
+ # - VM.snapshot_with_quiesce
352
+ class VMSnapshotWithQuiesceFailed < GenericError; end
353
+
354
+ # The VSS plug-in is not installed on this virtual machine
355
+ #
356
+ # Raised by
357
+ # - VM.snapshot_with_quiesce
358
+ class VMSnapshotWithQuiesceNotSupported < GenericError; end
359
+
360
+ # The VSS plug-in cannot be contacted
361
+ #
362
+ # Raised by
363
+ # - VM.snapshot_with_quiesce
364
+ class VMSnapshotWithQuiescePluginDoesNotRespond < GenericError; end
365
+
366
+ # The VSS plug-in has timed out
367
+ #
368
+ # Raised by
369
+ # - VM.snapshot_with_quiesce
370
+ class VMSnapshotWithQuiesceTimeout < GenericError; end
371
+
372
+ # Returns the class for the exception appropriate for the error description given
373
+ #
374
+ # @param [String] desc ErrorDescription value from the API
375
+ # @return [Class] Appropriate exception class for the given description
376
+ def self.exception_class_from_desc(desc)
377
+ case desc
378
+ when 'BOOTLOADER_FAILED'
379
+ BootloaderFailed
380
+ when 'DEVICE_ALREADY_DETACHED'
381
+ DeviceAlreadyDetached
382
+ when 'DEVICE_DETACH_REJECTED'
383
+ DeviceDetachRejected
384
+ when 'EVENTS_LOST'
385
+ EventsLost
386
+ when 'HA_OPERATION_WOULD_BREAK_FAILOVER_PLAN'
387
+ HAOperationWouldBreakFailoverPlan
388
+ when 'HOST_IS_SLAVE'
389
+ HostIsSlave
390
+ when 'HOST_NAME_INVALID'
391
+ HostNameInvalid
392
+ when 'HOST_NOT_ENOUGH_FREE_MEMORY'
393
+ HostNotEnoughFreeMemory
394
+ when 'IS_TUNNEL_ACCESS_PIF'
395
+ IsTunnelAccessPIF
396
+ when 'JOINING_HOST_CANNOT_CONTAIN_SHARED_SRS'
397
+ JoiningHostCannotContainSharedSRs
398
+ when 'LICENCE_RESTRICTION'
399
+ LicenceRestriction
400
+ when 'LICENSE_PROCESSING_ERROR'
401
+ LicenseProcessingError
402
+ when 'NO_HOSTS_AVAILABLE'
403
+ NoHostsAvailable
404
+ when 'OPENVSWITCH_NOT_ACTIVE'
405
+ OpenvswitchNotActive
406
+ when 'OPERATION_NOT_ALLOWED'
407
+ OperationNotAllowed
408
+ when 'OTHER_OPERATION_IN_PROGRESS'
409
+ OtherOperationInProgress
410
+ when 'PIF_IS_PHYSICAL'
411
+ PIFIsPhysical
412
+ when 'PIF_TUNNEL_STILL_EXISTS'
413
+ PIFTunnelStillExists
414
+ when 'SESSION_AUTHENTICATION_FAILED'
415
+ SessionAuthenticationFailed
416
+ when 'SESSION_NOT_REGISTERED'
417
+ SessionNotRegistered
418
+ when 'SR_FULL'
419
+ SRFull
420
+ when 'SR_HAS_PDB'
421
+ SRHasPDB
422
+ when 'SR_OPERATION_NOT_SUPPORTED'
423
+ SROperationNotSupported
424
+ when 'SR_UNKNOWN_DRIVER'
425
+ SRUnknownDriver
426
+ when 'TRANSPORT_PIF_NOT_CONFIGURED'
427
+ TransportPIFNotConfigured
428
+ when 'UNKNOWN_BOOTLOADER'
429
+ UnknownBootloader
430
+ when 'VBD_IS_EMPTY'
431
+ VBDIsEmpty
432
+ when 'VBD_NOT_EMPTY'
433
+ VBDNotEmpty
434
+ when 'VBD_NOT_REMOVABLE_MEDIA'
435
+ VBDNotRemovableMedia
436
+ when 'VLAN_TAG_INVALID'
437
+ VlanTagInvalid
438
+ when 'VM_BAD_POWER_STATE'
439
+ VMBadPowerState
440
+ when 'VM_CHECKPOINT_RESUME_FAILED'
441
+ VMCheckpointResumeFailed
442
+ when 'VM_CHECKPOINT_SUSPEND_FAILED'
443
+ VMCheckpointSuspendFailed
444
+ when 'VM_HVM_REQUIRED'
445
+ VMHVMRequired
446
+ when 'VM_IS_TEMPLATE'
447
+ VMIsTemplate
448
+ when 'VM_MIGRATE_FAILED'
449
+ VMMigrateFailed
450
+ when 'VM_MISSING_PV_DRIVERS'
451
+ VMMissingPVDrivers
452
+ when 'VM_REQUIRES_SR'
453
+ VMRequiresSR
454
+ when 'VM_REVERT_FAILED'
455
+ VMRevertFailed
456
+ when 'VM_SNAPSHOT_WITH_QUIESCE_FAILED'
457
+ VMSnapshotWithQuiesceFailed
458
+ when 'VM_SNAPSHOT_WITH_QUIESCE_NOT_SUPPORTED'
459
+ VMSnapshotWithQuiesceNotSupported
460
+ when 'VM_SNAPSHOT_WITH_QUIESCE_PLUGIN_DOES_NOT_RESPOND'
461
+ VMSnapshotWithQuiescePluginDoesNotRespond
462
+ when 'VM_SNAPSHOT_WITH_QUIESCE_TIMEOUT'
463
+ VMSnapshotWithQuiesceTimeout
464
+ else
465
+ GenericError
466
+ end
467
+ end
468
+ end
469
+ end
@@ -0,0 +1,26 @@
1
+ module XenApi
2
+ class XMLRPCClient < ::XMLRPC::Client
3
+ def initialize(host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil,
4
+ user=nil, password=nil, use_ssl=nil, timeout=nil)
5
+
6
+ if use_ssl == :verify_none
7
+ use_ssl = :verify_none
8
+ elsif !!use_ssl
9
+ use_ssl = :verify_peer
10
+ end
11
+
12
+ super(host, path, port, proxy_host, proxy_port, user, password, !!use_ssl, timeout)
13
+
14
+ case use_ssl
15
+ when :verify_peer
16
+ store = OpenSSL::X509::Store.new
17
+ store.set_default_paths
18
+ @http.cert_store = store
19
+ @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
20
+ when :verify_none
21
+ warn "warning: peer certificate won't be verified in this SSL session"
22
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
23
+ end
24
+ end
25
+ end
26
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-xapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-26 00:00:00.000000000 Z
12
+ date: 2012-10-31 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: chef
@@ -27,22 +27,6 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: 0.9.14
30
- - !ruby/object:Gem::Dependency
31
- name: xenapi
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ! '>='
36
- - !ruby/object:Gem::Version
37
- version: '0'
38
- type: :runtime
39
- prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ! '>='
44
- - !ruby/object:Gem::Version
45
- version: '0'
46
30
  - !ruby/object:Gem::Dependency
47
31
  name: highline
48
32
  requirement: !ruby/object:Gem::Requirement
@@ -93,6 +77,14 @@ files:
93
77
  - lib/chef/knife/xapi_vdi_detach.rb
94
78
  - lib/chef/knife/xapi_vdi_list.rb
95
79
  - lib/knife-xapi/version.rb
80
+ - lib/xenapi/README
81
+ - lib/xenapi/xen_api.rb
82
+ - lib/xenapi/xenapi.rb
83
+ - lib/xenapi/xenapi/async_dispatcher.rb
84
+ - lib/xenapi/xenapi/client.rb
85
+ - lib/xenapi/xenapi/dispatcher.rb
86
+ - lib/xenapi/xenapi/errors.rb
87
+ - lib/xenapi/xenapi/xmlrpc_client.rb
96
88
  homepage: https://github.com/spheromak/knife-xapi
97
89
  licenses: []
98
90
  post_install_message:
@@ -113,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
105
  version: '0'
114
106
  requirements: []
115
107
  rubyforge_project:
116
- rubygems_version: 1.8.23
108
+ rubygems_version: 1.8.24
117
109
  signing_key:
118
110
  specification_version: 3
119
111
  summary: Xen API Support for Chef's Knife Command