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.
- data/lib/chef/knife/xapi_base.rb +30 -14
- data/lib/chef/knife/xapi_guest_create.rb +39 -26
- data/lib/knife-xapi/version.rb +1 -1
- data/lib/xenapi/README +3 -0
- data/lib/xenapi/xen_api.rb +1 -0
- data/lib/xenapi/xenapi.rb +47 -0
- data/lib/xenapi/xenapi/async_dispatcher.rb +36 -0
- data/lib/xenapi/xenapi/client.rb +382 -0
- data/lib/xenapi/xenapi/dispatcher.rb +48 -0
- data/lib/xenapi/xenapi/errors.rb +469 -0
- data/lib/xenapi/xenapi/xmlrpc_client.rb +26 -0
- metadata +11 -19
data/lib/chef/knife/xapi_base.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
274
|
-
sr_ref =
|
275
|
-
|
276
|
-
|
277
|
-
|
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
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
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(
|
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)} "
|
data/lib/knife-xapi/version.rb
CHANGED
data/lib/xenapi/README
ADDED
@@ -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.
|
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-
|
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.
|
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
|