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