knife-xapi 0.4.3 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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