xenapi 0.2.7
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/.yardopts +2 -0
- data/HISTORY +45 -0
- data/LICENSE +20 -0
- data/README.rdoc +28 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/lib/xen_api.rb +1 -0
- data/lib/xenapi.rb +6 -0
- data/lib/xenapi/async_dispatcher.rb +36 -0
- data/lib/xenapi/client.rb +258 -0
- data/lib/xenapi/dispatcher.rb +48 -0
- data/lib/xenapi/errors.rb +451 -0
- data/test/helper.rb +10 -0
- data/test/test_xenapi.rb +7 -0
- data/xenapi.gemspec +53 -0
- metadata +93 -0
data/.yardopts
ADDED
data/HISTORY
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
= XenApi History
|
2
|
+
|
3
|
+
=== 0.2.7 - 2011-05-09
|
4
|
+
* Add specific Exceptions for Xen API method errors. These are kept under XenApi::Errors.
|
5
|
+
* Massive internal cleanup and documentation
|
6
|
+
|
7
|
+
=== 0.2.6 - 2011-05-06
|
8
|
+
* [Enhancement]: Changed XenApi::Client#after_login to return self. Allows for adding
|
9
|
+
an #after_login block while creating the XenApi::Client such as
|
10
|
+
|
11
|
+
client = XenApi::Client.new("http://192.168.1.2").after_login do |c|
|
12
|
+
c.event.register(['vm'])
|
13
|
+
end
|
14
|
+
|
15
|
+
=== 0.2.5 - 2010-02-16
|
16
|
+
* [BugFix]: Fix issues with API calls which are passed arrays as their arguments
|
17
|
+
|
18
|
+
=== 0.2.4 - 2010-02-16
|
19
|
+
* [BugFix]: Fix undef of clone method on Dispatcher
|
20
|
+
|
21
|
+
=== 0.2.3 - 2010-02-16
|
22
|
+
* [BugFix]: Reattempted calls would fail after a re-login occurred
|
23
|
+
|
24
|
+
=== 0.2.2 - 2010-02-16
|
25
|
+
* Prevent the Ruby :clone method from masking the API method
|
26
|
+
|
27
|
+
=== 0.2.1 - 2010-02-15
|
28
|
+
* Allow for calling the after_login with the client object
|
29
|
+
|
30
|
+
=== 0.2.0 - 2010-02-15
|
31
|
+
* Add after_login callback
|
32
|
+
* Expose Session as xenapi_session
|
33
|
+
|
34
|
+
=== 0.1.2 - 2010-02-15
|
35
|
+
* Prevent explosions when an empty path is provided to the XMLRPC::Client
|
36
|
+
|
37
|
+
=== 0.1.1 - 2010-02-15
|
38
|
+
* Codename: I find your lack of tests disturbing
|
39
|
+
* Correct async => Async
|
40
|
+
|
41
|
+
=== 0.1.0 - 2010-02-15
|
42
|
+
* Added support for 'async' prefixed requests
|
43
|
+
|
44
|
+
=== 0.0.0 - 2010-02-11
|
45
|
+
* Initial Release
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Geoff Garside
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
= XenApi
|
2
|
+
|
3
|
+
Xen API XMLRPC Client library for working with XenServers
|
4
|
+
|
5
|
+
== Quick Example
|
6
|
+
|
7
|
+
In this quick example we connect, login and invoke an API method.
|
8
|
+
|
9
|
+
client = XenApi::Client.new('http://xenapi.test')
|
10
|
+
client.login_with_password('root', 'password')
|
11
|
+
client.VM.get_all
|
12
|
+
|
13
|
+
This will likely be enough information to get you on your feet with
|
14
|
+
the API, for more details see {XenApi::Client}.
|
15
|
+
|
16
|
+
== Note on Patches/Pull Requests
|
17
|
+
|
18
|
+
* Fork the project.
|
19
|
+
* Make your feature addition or bug fix.
|
20
|
+
* Add tests for it. This is important so I don't break it in a
|
21
|
+
future version unintentionally.
|
22
|
+
* Commit, do not mess with rakefile, version, or history.
|
23
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
24
|
+
* Send me a pull request. Bonus points for topic branches.
|
25
|
+
|
26
|
+
== Copyright
|
27
|
+
|
28
|
+
Copyright (c) 2010 Geoff Garside. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "xenapi"
|
8
|
+
gem.summary = %Q{Xen API XMLRPC Client}
|
9
|
+
gem.description = %Q{Xen API XMLRPC Client library for working with XenServers}
|
10
|
+
gem.email = "geoff+xenapi@geoffgarside.co.uk"
|
11
|
+
gem.homepage = "http://github.com/geoffgarside/xenapi"
|
12
|
+
gem.authors = ["Geoff Garside"]
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "xenapi #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
54
|
+
|
55
|
+
begin
|
56
|
+
require 'yard'
|
57
|
+
YARD::Rake::YardocTask.new { |y| y.options += %w(--no-private) }
|
58
|
+
YARD::Rake::YardocTask.new('yard:dev') { |y| y.options += %w(--protected --private) }
|
59
|
+
rescue LoadError
|
60
|
+
task :yardoc do
|
61
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
62
|
+
end
|
63
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.7
|
data/lib/xen_api.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'xenapi'
|
data/lib/xenapi.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
module XenApi #:nodoc:
|
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 :AsyncDispatch, File.expand_path('../xenapi/async_dispatcher', __FILE__)
|
6
|
+
end
|
@@ -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,258 @@
|
|
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
|
+
# client.Async.VM.get_all
|
51
|
+
# client.async.VM.get_all
|
52
|
+
#
|
53
|
+
# Calling either +Async+ or +async+ will work as the
|
54
|
+
# capitalised form will always be sent when calling
|
55
|
+
# a method asynchronously.
|
56
|
+
class Client
|
57
|
+
# The +LoginRequired+ exception is raised when
|
58
|
+
# an API request requires login and no login
|
59
|
+
# credentials have yet been provided.
|
60
|
+
#
|
61
|
+
# If you don't perform a login before receiving this
|
62
|
+
# exception then you will want to catch it, log into
|
63
|
+
# the API and then retry your request.
|
64
|
+
class LoginRequired < RuntimeError; end
|
65
|
+
|
66
|
+
# The +SessionInvalid+ exception is raised when the
|
67
|
+
# API session has become stale or is otherwise invalid.
|
68
|
+
#
|
69
|
+
# Internally this exception will be handled a number of
|
70
|
+
# times before being raised up to the calling code.
|
71
|
+
class SessionInvalid < RuntimeError; end
|
72
|
+
|
73
|
+
# The +ResponseMissingStatusField+ exception is raised
|
74
|
+
# when the XMLRPC response is missing the +Status+ field.
|
75
|
+
# This typically indicates an unrecoverable error with
|
76
|
+
# the API itself.
|
77
|
+
class ResponseMissingStatusField < RuntimeError; end
|
78
|
+
|
79
|
+
# The +ResponseMissingValueField+ exception is raised
|
80
|
+
# when the XMLRPC response is missing the +Value+ field.
|
81
|
+
# This typically indicates an unrecoverable error with
|
82
|
+
# the API itself.
|
83
|
+
class ResponseMissingValueField < RuntimeError; end
|
84
|
+
|
85
|
+
# The +ResponseMissingErrorDescriptionField+ exception
|
86
|
+
# is raised when an error is returned in the XMLRPC
|
87
|
+
# response, but the type of error cannot be determined
|
88
|
+
# due to the lack of the +ErrorDescription+ field.
|
89
|
+
class ResponseMissingErrorDescriptionField < RuntimeError; end
|
90
|
+
|
91
|
+
# @see Object#inspect
|
92
|
+
def inspect
|
93
|
+
"#<#{self.class} #{@uri}>"
|
94
|
+
end
|
95
|
+
|
96
|
+
# @param [String] uri URL to the Xen API endpoint
|
97
|
+
# @param [Integer] timeout Maximum number of seconds to wait for an API response
|
98
|
+
def initialize(uri, timeout = 10)
|
99
|
+
@timeout = timeout
|
100
|
+
@uri = URI.parse(uri)
|
101
|
+
@uri.path = '/' if @uri.path == ''
|
102
|
+
end
|
103
|
+
|
104
|
+
# @overload after_login
|
105
|
+
# Adds a block to be called after successful login to the XenAPI.
|
106
|
+
# @note The block will be called whenever the receiver has to authenticate
|
107
|
+
# with the XenAPI. This includes the first time the receiver recieves a
|
108
|
+
# +login_*+ method call and any time the session becomes invalid.
|
109
|
+
# @yield client
|
110
|
+
# @yieldparam [optional, Client] client Client instance
|
111
|
+
# @overload after_login
|
112
|
+
# Calls the created block, this is primarily for internal use only
|
113
|
+
# @return [Client] receiver
|
114
|
+
def after_login(&block)
|
115
|
+
if block
|
116
|
+
@after_login = block
|
117
|
+
elsif @after_login
|
118
|
+
case @after_login.arity
|
119
|
+
when 1
|
120
|
+
@after_login.call(self)
|
121
|
+
else
|
122
|
+
@after_login.call
|
123
|
+
end
|
124
|
+
end
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns the current session identifier.
|
129
|
+
#
|
130
|
+
# @return [String] session identifier
|
131
|
+
def xenapi_session
|
132
|
+
@session
|
133
|
+
end
|
134
|
+
|
135
|
+
# Handle API method calls.
|
136
|
+
#
|
137
|
+
# If the method called starts with +login+ then the method is
|
138
|
+
# assumed to be part of the +session+ namespace and will be
|
139
|
+
# called directly. For example +login_with_password+
|
140
|
+
#
|
141
|
+
# client = XenApi::Client.new('http://xenapi.test/')
|
142
|
+
# client.login_with_password('root', 'password)
|
143
|
+
#
|
144
|
+
# If the method called is +async+ then an +AsyncDispatcher+
|
145
|
+
# will be created to handle the asynchronous API method call.
|
146
|
+
#
|
147
|
+
# client = XenApi::Client.new('http://xenapi.test/')
|
148
|
+
# client.async.host.get_servertime(ref)
|
149
|
+
#
|
150
|
+
# The final case will create a +Dispatcher+ to handle the
|
151
|
+
# subsequent method call such as.
|
152
|
+
#
|
153
|
+
# client = XenApi::Client.new('http://xenapi.test/')
|
154
|
+
# client.host.get_servertime(ref)
|
155
|
+
#
|
156
|
+
# @note +meth+ names are not validated
|
157
|
+
#
|
158
|
+
# @param [String,Symbol] meth Method name
|
159
|
+
# @param [...] args Method args
|
160
|
+
# @return [true,AsyncDispatcher,Dispatcher]
|
161
|
+
def method_missing(meth, *args)
|
162
|
+
case meth.to_s
|
163
|
+
when /^login/
|
164
|
+
_login(meth, *args)
|
165
|
+
when /^async/i
|
166
|
+
AsyncDispatcher.new(self, :_call)
|
167
|
+
else
|
168
|
+
Dispatcher.new(self, meth, :_call)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
protected
|
172
|
+
# @param [String,Symbol] meth API method to call
|
173
|
+
# @param [Array] args Arguments to pass to the method call
|
174
|
+
# @raise [SessionInvalid] Reauthentication failed
|
175
|
+
# @raise [LoginRequired] Authentication required, unable to login automatically
|
176
|
+
# @raise [EOFError] XMLRPC::Client exception
|
177
|
+
# @raise [Errno::EPIPE] XMLRPC::Client exception
|
178
|
+
def _call(meth, *args)
|
179
|
+
begin
|
180
|
+
_do_call(meth, args.dup.unshift(@session))
|
181
|
+
rescue SessionInvalid
|
182
|
+
_relogin_attempts = (_relogin_attempts || 0) + 1
|
183
|
+
_relogin
|
184
|
+
retry unless _relogin_attempts > 2
|
185
|
+
raise
|
186
|
+
rescue EOFError
|
187
|
+
_eof_retries = (_eof_retries || 0) + 1
|
188
|
+
@client = nil
|
189
|
+
retry unless _eof_retries > 1
|
190
|
+
raise
|
191
|
+
rescue Errno::EPIPE
|
192
|
+
_epipe_retries = (_epipe_retries || 0) + 1
|
193
|
+
@client = nil
|
194
|
+
retry unless _epipe_retries > 1
|
195
|
+
raise
|
196
|
+
end
|
197
|
+
end
|
198
|
+
private
|
199
|
+
# Reauthenticate with the API
|
200
|
+
# @raise [LoginRequired] Missing authentication credentials
|
201
|
+
def _relogin
|
202
|
+
raise LoginRequired if @login_meth.nil? || @login_args.nil? || @login_args.empty?
|
203
|
+
_login(@login_meth, *@login_args)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Login to the API
|
207
|
+
#
|
208
|
+
# @note Will call the +after_login+ block if login is successful
|
209
|
+
#
|
210
|
+
# @param [String,Symbol] meth Login method name
|
211
|
+
# @param [...] args Arguments to pass to the login method
|
212
|
+
# @return [Boolean] true
|
213
|
+
# @raise [Exception] any exception raised by +_do_call+ or +after_login+
|
214
|
+
def _login(meth, *args)
|
215
|
+
begin
|
216
|
+
@session = _do_call("session.#{meth}", args)
|
217
|
+
@login_meth = meth
|
218
|
+
@login_args = args
|
219
|
+
after_login
|
220
|
+
true
|
221
|
+
rescue Exception => e
|
222
|
+
raise e
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Return or initialize new +XMLRPC::Client+
|
227
|
+
#
|
228
|
+
# @return [XMLRPC::Client] XMLRPC client instance
|
229
|
+
def _client
|
230
|
+
@client ||= XMLRPC::Client.new(@uri.host, @uri.path, @uri.port, nil, nil, nil, nil, @uri.port == 443, @timeout)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Perform XMLRPC method call.
|
234
|
+
#
|
235
|
+
# @param [String,Symbol] meth XMLRPC method to call
|
236
|
+
# @param [Array] args XMLRPC method arguments
|
237
|
+
# @param [Integer] attempts Number of times to retry the call, presently unused
|
238
|
+
# @return [Object] method return value
|
239
|
+
# @raise [ResponseMissingStatusField] XMLRPC response does not have a +Status+ field
|
240
|
+
# @raise [ResponseMissingValueField] XMLRPC response does not have a +Value+ field
|
241
|
+
# @raise [ResponseMissingErrorDescriptionField] API response error missing +ErrorDescription+ field
|
242
|
+
# @raise [SessionInvalid] API session has expired
|
243
|
+
# @raise [Errors::GenericError] API method specific error
|
244
|
+
def _do_call(meth, args, attempts = 3)
|
245
|
+
r = _client.call(meth, *args)
|
246
|
+
raise ResponseMissingStatusField unless r.has_key?('Status')
|
247
|
+
|
248
|
+
if r['Status'] == 'Success'
|
249
|
+
return r['Value'] if r.has_key?('Value')
|
250
|
+
raise ResponseMissingValueField
|
251
|
+
else
|
252
|
+
raise ResponseMissingErrorDescriptionField unless r.has_key?('ErrorDescription')
|
253
|
+
raise SessionInvalid if r['ErrorDescription'][0] == 'SESSION_INVALID'
|
254
|
+
raise Errors.exception_class_from_desc(r['ErrorDescription'].shift), r['ErrorDescription'].inspect
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
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,451 @@
|
|
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; end
|
5
|
+
|
6
|
+
# The bootloader returned an error.
|
7
|
+
#
|
8
|
+
# Raised by
|
9
|
+
# - VM.start
|
10
|
+
# - VM.start_on
|
11
|
+
class BootloaderFailed < GenericError; end
|
12
|
+
|
13
|
+
# The device is not currently attached
|
14
|
+
#
|
15
|
+
# Raised by
|
16
|
+
# - VBD.unplug
|
17
|
+
class DeviceAlreadyDetached < GenericError; end
|
18
|
+
|
19
|
+
# The VM rejected the attempt to detach the device.
|
20
|
+
#
|
21
|
+
# Raised by
|
22
|
+
# - VBD.unplug
|
23
|
+
class DeviceDetachRejected < GenericError; end
|
24
|
+
|
25
|
+
# Some events have been lost from the queue and cannot be retrieved.
|
26
|
+
#
|
27
|
+
# Raised by
|
28
|
+
# - event.next
|
29
|
+
class EventsLost < GenericError; end
|
30
|
+
|
31
|
+
# This operation cannot be performed because it would invalidate VM
|
32
|
+
# failover planning such that the system would be unable to guarantee
|
33
|
+
# to restart protected VMs after a Host failure.
|
34
|
+
#
|
35
|
+
# Raised by
|
36
|
+
# - VM.set_memory_static_max
|
37
|
+
class HAOperationWouldBreakFailoverPlan < GenericError; end
|
38
|
+
|
39
|
+
# The host name is invalid
|
40
|
+
#
|
41
|
+
# Raised by
|
42
|
+
# - host.set_hostname_live
|
43
|
+
class HostNameInvalid < GenericError; end
|
44
|
+
|
45
|
+
# Not enough host memory is available to perform this operation
|
46
|
+
#
|
47
|
+
# Raised by
|
48
|
+
# - VM.assert_can_boot_here
|
49
|
+
class HostNotEnoughFreeMemory < GenericError; end
|
50
|
+
|
51
|
+
# You tried to create a VLAN or tunnel on top of a tunnel access
|
52
|
+
# PIF - use the underlying transport PIF instead.
|
53
|
+
#
|
54
|
+
# Raised by
|
55
|
+
# - tunnel.create
|
56
|
+
class IsTunnelAccessPIF < GenericError; end
|
57
|
+
|
58
|
+
# The host joining the pool cannot contain any shared storage.
|
59
|
+
#
|
60
|
+
# Raised by
|
61
|
+
# - pool.join
|
62
|
+
class JoiningHostCannotContainSharedSRs < GenericError; end
|
63
|
+
|
64
|
+
# This operation is not allowed under your license.
|
65
|
+
# Please contact your support representative.
|
66
|
+
#
|
67
|
+
# Raised by
|
68
|
+
# - VM.start
|
69
|
+
class LicenceRestriction < GenericError; end
|
70
|
+
|
71
|
+
# There was an error processing your license. Please contact
|
72
|
+
# your support representative.
|
73
|
+
#
|
74
|
+
# Raised by
|
75
|
+
# - host.license_apply
|
76
|
+
class LicenseProcessingError < GenericError; end
|
77
|
+
|
78
|
+
# There were no hosts available to complete the specified operation.
|
79
|
+
#
|
80
|
+
# Raised by
|
81
|
+
# - VM.start
|
82
|
+
class NoHostsAvailable < GenericError; end
|
83
|
+
|
84
|
+
# This operation needs the OpenVSwitch networking backend to be
|
85
|
+
# enabled on all hosts in the pool.
|
86
|
+
#
|
87
|
+
# Raised by
|
88
|
+
# - tunnel.create
|
89
|
+
class OpenvswitchNotActive < GenericError; end
|
90
|
+
|
91
|
+
# You attempted an operation that was not allowed.
|
92
|
+
#
|
93
|
+
# Raised by
|
94
|
+
# - task.cancel
|
95
|
+
# - VM.checkpoint
|
96
|
+
# - VM.clean_reboot
|
97
|
+
# - VM.clean_shutdown
|
98
|
+
# - VM.clone
|
99
|
+
# - VM.copy
|
100
|
+
# - VM.hard_reboot
|
101
|
+
# - VM.hard_shutdown
|
102
|
+
# - VM.pause
|
103
|
+
# - VM.pool_migrate
|
104
|
+
# - VM.provision
|
105
|
+
# - VM.resume
|
106
|
+
# - VM.resume_on
|
107
|
+
# - VM.revert
|
108
|
+
# - VM.snapshot
|
109
|
+
# - VM.snapshot_with_quiesce
|
110
|
+
# - VM.start
|
111
|
+
# - VM.start_on
|
112
|
+
# - VM.suspend
|
113
|
+
# - VM.unpause
|
114
|
+
class OperationNotAllowed < GenericError; end
|
115
|
+
|
116
|
+
# Another operation involving the object is currently in progress
|
117
|
+
#
|
118
|
+
# Raised by
|
119
|
+
# - VM.clean_reboot
|
120
|
+
# - VM.clean_shutdown
|
121
|
+
# - VM.hard_reboot
|
122
|
+
# - VM.hard_shutdown
|
123
|
+
# - VM.pause
|
124
|
+
# - VM.pool_migrate
|
125
|
+
# - VM.start
|
126
|
+
# - VM.start_on
|
127
|
+
# - VM.suspend
|
128
|
+
class OtherOperationInProgress < GenericError; end
|
129
|
+
|
130
|
+
# You tried to destroy a PIF, but it represents an aspect of the physical
|
131
|
+
# host configuration, and so cannot be destroyed. The parameter echoes the
|
132
|
+
# PIF handle you gave.
|
133
|
+
#
|
134
|
+
# Raised by
|
135
|
+
# - PIF.destroy
|
136
|
+
# @deprecated the PIF.destroy method is deprecated in XenServer 4.1 and replaced
|
137
|
+
# by VLAN.destroy and Bond.destroy
|
138
|
+
class PIFIsPhysical < GenericError; end
|
139
|
+
|
140
|
+
# Operation cannot proceed while a tunnel exists on this interface.
|
141
|
+
#
|
142
|
+
# Raised by
|
143
|
+
# - PIF.forget
|
144
|
+
class PIFTunnelStillExists < GenericError; end
|
145
|
+
|
146
|
+
# The credentials given by the user are incorrect, so access has been
|
147
|
+
# denied, and you have not been issued a session handle.
|
148
|
+
#
|
149
|
+
# Raised by
|
150
|
+
# - session.login_with_password
|
151
|
+
class SessionAuthenticationFailed < GenericError; end
|
152
|
+
|
153
|
+
# This session is not registered to receive events. You must call
|
154
|
+
# event.register before event.next. The session handle you are
|
155
|
+
# using is echoed.
|
156
|
+
#
|
157
|
+
# Raised by
|
158
|
+
# - event.next
|
159
|
+
class SessionNotRegistered < GenericError; end
|
160
|
+
|
161
|
+
# The SR is full. Requested new size exceeds the maximum size
|
162
|
+
#
|
163
|
+
# Raised by
|
164
|
+
# - VM.checkpoint
|
165
|
+
# - VM.clone
|
166
|
+
# - VM.copy
|
167
|
+
# - VM.provision
|
168
|
+
# - VM.revert
|
169
|
+
# - VM.snapshot
|
170
|
+
# - VM.snapshot_with_quiesce
|
171
|
+
class SRFull < GenericError; end
|
172
|
+
|
173
|
+
# The SR is still connected to a host via a PBD. It cannot be destroyed.
|
174
|
+
#
|
175
|
+
# Raised by
|
176
|
+
# - SR.destroy
|
177
|
+
# - SR.forget
|
178
|
+
class SRHasPDB < GenericError; end
|
179
|
+
|
180
|
+
# The SR backend does not support the operation (check the SR's allowed operations)
|
181
|
+
#
|
182
|
+
# Raised by
|
183
|
+
# - VDI.introduce
|
184
|
+
# - VDI.update
|
185
|
+
class SROperationNotSupported < GenericError; end
|
186
|
+
|
187
|
+
# The SR could not be connected because the driver was not recognised.
|
188
|
+
#
|
189
|
+
# Raised by
|
190
|
+
# - PBD.plug
|
191
|
+
# - SR.create
|
192
|
+
class SRUnknownDriver < GenericError; end
|
193
|
+
|
194
|
+
# The tunnel transport PIF has no IP configuration set.
|
195
|
+
#
|
196
|
+
# Raised by
|
197
|
+
# - PIF.plug
|
198
|
+
# - tunnel.create
|
199
|
+
class TransportPIFNotConfigured < GenericError; end
|
200
|
+
|
201
|
+
# The requested bootloader is unknown
|
202
|
+
#
|
203
|
+
# Raised by
|
204
|
+
# - VM.start
|
205
|
+
# - VM.start_on
|
206
|
+
class UnknownBootloader < GenericError; end
|
207
|
+
|
208
|
+
# Operation could not be performed because the drive is empty
|
209
|
+
#
|
210
|
+
# Raised by
|
211
|
+
# - VBD.eject
|
212
|
+
class VBDIsEmpty < GenericError; end
|
213
|
+
|
214
|
+
# Operation could not be performed because the drive is not empty
|
215
|
+
#
|
216
|
+
# Raised by
|
217
|
+
# - VBD.insert
|
218
|
+
class VBDNotEmpty < GenericError; end
|
219
|
+
|
220
|
+
# Media could not be ejected because it is not removable
|
221
|
+
#
|
222
|
+
# Raised by
|
223
|
+
# - VBD.eject
|
224
|
+
# - VBD.insert
|
225
|
+
class VBDNotRemovableMedia < GenericError; end
|
226
|
+
|
227
|
+
# You tried to create a VLAN, but the tag you gave was invalid --
|
228
|
+
# it must be between 0 and 4094. The parameter echoes the VLAN tag you gave.
|
229
|
+
#
|
230
|
+
# Raised by
|
231
|
+
# - PIF.create_VLAN (deprecated)
|
232
|
+
# - pool.create_VLAN (deprecated)
|
233
|
+
# - pool.create_VLAN_from_PIF
|
234
|
+
class VlanTagInvalid < GenericError; end
|
235
|
+
|
236
|
+
# You attempted an operation on a VM that was not in an appropriate
|
237
|
+
# power state at the time; for example, you attempted to start a VM
|
238
|
+
# that was already running. The parameters returned are the VM's
|
239
|
+
# handle, and the expected and actual VM state at the time of the call.
|
240
|
+
#
|
241
|
+
# Raised by
|
242
|
+
# - VM.checkpoint
|
243
|
+
# - VM.clean_reboot
|
244
|
+
# - VM.clean_shutdown
|
245
|
+
# - VM.checkpoint
|
246
|
+
# - VM.clean_reboot
|
247
|
+
# - VM.clean_shutdown
|
248
|
+
# - VM.clone
|
249
|
+
# - VM.copy
|
250
|
+
# - VM.hard_reboot
|
251
|
+
# - VM.hard_shutdown
|
252
|
+
# - VM.pause
|
253
|
+
# - VM.pool_migrate
|
254
|
+
# - VM.provision
|
255
|
+
# - VM.resume
|
256
|
+
# - VM.resume_on
|
257
|
+
# - VM.revert
|
258
|
+
# - VM.send_sysrq
|
259
|
+
# - VM.send_trigger
|
260
|
+
# - VM.snapshot
|
261
|
+
# - VM.snapshot_with_quiesce
|
262
|
+
# - VM.start
|
263
|
+
# - VM.start_on
|
264
|
+
# - VM.suspend
|
265
|
+
# - VM.unpause
|
266
|
+
class VMBadPowerState < GenericError; end
|
267
|
+
|
268
|
+
# An error occured while restoring the memory image of the
|
269
|
+
# specified virtual machine
|
270
|
+
#
|
271
|
+
# Raised by
|
272
|
+
# - VM.checkpoint
|
273
|
+
class VMCheckpointResumeFailed < GenericError; end
|
274
|
+
|
275
|
+
# An error occured while saving the memory image of the
|
276
|
+
# specified virtual machine
|
277
|
+
#
|
278
|
+
# Raised by
|
279
|
+
# - VM.checkpoint
|
280
|
+
class VMCheckpointSuspendFailed < GenericError; end
|
281
|
+
|
282
|
+
# HVM is required for this operation
|
283
|
+
#
|
284
|
+
# Raised by
|
285
|
+
# - VM.start
|
286
|
+
class VMHvmRequired < GenericError; end
|
287
|
+
|
288
|
+
# The operation attempted is not valid for a template VM
|
289
|
+
#
|
290
|
+
# Raised by
|
291
|
+
# - VM.clean_reboot
|
292
|
+
# - VM.clean_shutdown
|
293
|
+
# - VM.hard_reboot
|
294
|
+
# - VM.hard_shutdown
|
295
|
+
# - VM.pause
|
296
|
+
# - VM.pool_migrate
|
297
|
+
# - VM.resume
|
298
|
+
# - VM.resume_on
|
299
|
+
# - VM.start
|
300
|
+
# - VM.start_on
|
301
|
+
# - VM.suspend
|
302
|
+
# - VM.unpause
|
303
|
+
class VMIsTemplate < GenericError; end
|
304
|
+
|
305
|
+
# An error occurred during the migration process.
|
306
|
+
#
|
307
|
+
# Raised by
|
308
|
+
# - VM.pool_migrate
|
309
|
+
class VMMigrateFailed < GenericError; end
|
310
|
+
|
311
|
+
# You attempted an operation on a VM which requires PV drivers
|
312
|
+
# to be installed but the drivers were not detected.
|
313
|
+
#
|
314
|
+
# Raised by
|
315
|
+
# -VM.pool_migrate
|
316
|
+
class VMMissingPVDrivers < GenericError; end
|
317
|
+
|
318
|
+
# You attempted to run a VM on a host which doesn't have access to an SR
|
319
|
+
# needed by the VM. The VM has at least one VBD attached to a VDI in the SR
|
320
|
+
#
|
321
|
+
# Raised by
|
322
|
+
# - VM.assert_can_boot_here
|
323
|
+
class VMRequiresSR < GenericError; end
|
324
|
+
|
325
|
+
# An error occured while reverting the specified virtual machine
|
326
|
+
# to the specified snapshot
|
327
|
+
#
|
328
|
+
# Raised by
|
329
|
+
# - VM.revert
|
330
|
+
class VMRevertFailed < GenericError; end
|
331
|
+
|
332
|
+
# The quiesced-snapshot operation failed for an unexpected reason
|
333
|
+
#
|
334
|
+
# Raised by
|
335
|
+
# - VM.snapshot_with_quiesce
|
336
|
+
class VMSnapshotWithQuiesceFailed < GenericError; end
|
337
|
+
|
338
|
+
# The VSS plug-in is not installed on this virtual machine
|
339
|
+
#
|
340
|
+
# Raised by
|
341
|
+
# - VM.snapshot_with_quiesce
|
342
|
+
class VMSnapshotWithQuiesceNotSupported < GenericError; end
|
343
|
+
|
344
|
+
# The VSS plug-in cannot be contacted
|
345
|
+
#
|
346
|
+
# Raised by
|
347
|
+
# - VM.snapshot_with_quiesce
|
348
|
+
class VMSnapshotWithQuiescePluginDoesNotRespond < GenericError; end
|
349
|
+
|
350
|
+
# The VSS plug-in has timed out
|
351
|
+
#
|
352
|
+
# Raised by
|
353
|
+
# - VM.snapshot_with_quiesce
|
354
|
+
class VMSnapshotWithQuiesceTimeout < GenericError; end
|
355
|
+
|
356
|
+
# Returns the class for the exception appropriate for the error description given
|
357
|
+
#
|
358
|
+
# @param [String] desc ErrorDescription value from the API
|
359
|
+
# @return [Class] Appropriate exception class for the given description
|
360
|
+
def self.exception_class_from_desc(desc)
|
361
|
+
case desc
|
362
|
+
when 'BOOTLOADER_FAILED'
|
363
|
+
BootloaderFailed
|
364
|
+
when 'DEVICE_ALREADY_DETACHED'
|
365
|
+
DeviceAlreadyDetached
|
366
|
+
when 'DEVICE_DETACH_REJECTED'
|
367
|
+
DeviceDetachRejected
|
368
|
+
when 'EVENTS_LOST'
|
369
|
+
EventsLost
|
370
|
+
when 'HA_OPERATION_WOULD_BREAK_FAILOVER_PLAN'
|
371
|
+
HAOperationWouldBreakFailoverPlan
|
372
|
+
when 'HOST_NAME_INVALID'
|
373
|
+
HostNameInvalid
|
374
|
+
when 'HOST_NOT_ENOUGH_FREE_MEMORY'
|
375
|
+
HostNotEnoughFreeMemory
|
376
|
+
when 'IS_TUNNEL_ACCESS_PIF'
|
377
|
+
IsTunnelAccessPIF
|
378
|
+
when 'JOINING_HOST_CANNOT_CONTAIN_SHARED_SRS'
|
379
|
+
JoiningHostCannotContainSharedSRs
|
380
|
+
when 'LICENCE_RESTRICTION'
|
381
|
+
LicenceRestriction
|
382
|
+
when 'LICENSE_PROCESSING_ERROR'
|
383
|
+
LicenseProcessingError
|
384
|
+
when 'NO_HOSTS_AVAILABLE'
|
385
|
+
NoHostsAvailable
|
386
|
+
when 'OPENVSWITCH_NOT_ACTIVE'
|
387
|
+
OpenvswitchNotActive
|
388
|
+
when 'OPERATION_NOT_ALLOWED'
|
389
|
+
OperationNotAllowed
|
390
|
+
when 'OTHER_OPERATION_IN_PROGRESS'
|
391
|
+
OtherOperationInProgress
|
392
|
+
when 'PIF_IS_PHYSICAL'
|
393
|
+
PIFIsPhysical
|
394
|
+
when 'PIF_TUNNEL_STILL_EXISTS'
|
395
|
+
PIFTunnelStillExists
|
396
|
+
when 'SESSION_AUTHENTICATION_FAILED'
|
397
|
+
SessionAuthenticationFailed
|
398
|
+
when 'SESSION_NOT_REGISTERED'
|
399
|
+
SessionNotRegistered
|
400
|
+
when 'SR_FULL'
|
401
|
+
SRFull
|
402
|
+
when 'SR_HAS_PDB'
|
403
|
+
SRHasPDB
|
404
|
+
when 'SR_OPERATION_NOT_SUPPORTED'
|
405
|
+
SROperationNotSupported
|
406
|
+
when 'SR_UNKNOWN_DRIVER'
|
407
|
+
SRUnknownDriver
|
408
|
+
when 'TRANSPORT_PIF_NOT_CONFIGURED'
|
409
|
+
TransportPIFNotConfigured
|
410
|
+
when 'UNKNOWN_BOOTLOADER'
|
411
|
+
UnknownBootloader
|
412
|
+
when 'VBD_IS_EMPTY'
|
413
|
+
VBDIsEmpty
|
414
|
+
when 'VBD_NOT_EMPTY'
|
415
|
+
VBDNotEmpty
|
416
|
+
when 'VBD_NOT_REMOVABLE_MEDIA'
|
417
|
+
VBDNotRemovableMedia
|
418
|
+
when 'VLAN_TAG_INVALID'
|
419
|
+
VlanTagInvalid
|
420
|
+
when 'VM_BAD_POWER_STATE'
|
421
|
+
VMBadPowerState
|
422
|
+
when 'VM_CHECKPOINT_RESUME_FAILED'
|
423
|
+
VMCheckpointResumeFailed
|
424
|
+
when 'VM_CHECKPOINT_SUSPEND_FAILED'
|
425
|
+
VMCheckpointSuspendFailed
|
426
|
+
when 'VM_HVM_REQUIRED'
|
427
|
+
VMHVMRequired
|
428
|
+
when 'VM_IS_TEMPLATE'
|
429
|
+
VMIsTemplate
|
430
|
+
when 'VM_MIGRATE_FAILED'
|
431
|
+
VMMigrateFailed
|
432
|
+
when 'VM_MISSING_PV_DRIVERS'
|
433
|
+
VMMissingPVDrivers
|
434
|
+
when 'VM_REQUIRES_SR'
|
435
|
+
VMRequiresSR
|
436
|
+
when 'VM_REVERT_FAILED'
|
437
|
+
VMRevertFailed
|
438
|
+
when 'VM_SNAPSHOT_WITH_QUIESCE_FAILED'
|
439
|
+
VMSnapshotWithQuiesceFailed
|
440
|
+
when 'VM_SNAPSHOT_WITH_QUIESCE_NOT_SUPPORTED'
|
441
|
+
VMSnapshotWithQuiesceNotSupported
|
442
|
+
when 'VM_SNAPSHOT_WITH_QUIESCE_PLUGIN_DOES_NOT_RESPOND'
|
443
|
+
VMSnapshotWithQuiescePluginDoesNotRespond
|
444
|
+
when 'VM_SNAPSHOT_WITH_QUIESCE_TIMEOUT'
|
445
|
+
VMSnapshotWithQuiesceTimeout
|
446
|
+
else
|
447
|
+
GenericError
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
data/test/helper.rb
ADDED
data/test/test_xenapi.rb
ADDED
data/xenapi.gemspec
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{xenapi}
|
8
|
+
s.version = "0.2.7"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Geoff Garside"]
|
12
|
+
s.date = %q{2011-05-09}
|
13
|
+
s.description = %q{Xen API XMLRPC Client library for working with XenServers}
|
14
|
+
s.email = %q{geoff+xenapi@geoffgarside.co.uk}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".yardopts",
|
21
|
+
"HISTORY",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/xen_api.rb",
|
27
|
+
"lib/xenapi.rb",
|
28
|
+
"lib/xenapi/async_dispatcher.rb",
|
29
|
+
"lib/xenapi/client.rb",
|
30
|
+
"lib/xenapi/dispatcher.rb",
|
31
|
+
"lib/xenapi/errors.rb",
|
32
|
+
"test/helper.rb",
|
33
|
+
"test/test_xenapi.rb",
|
34
|
+
"xenapi.gemspec"
|
35
|
+
]
|
36
|
+
s.homepage = %q{http://github.com/geoffgarside/xenapi}
|
37
|
+
s.require_paths = ["lib"]
|
38
|
+
s.rubygems_version = %q{1.7.2}
|
39
|
+
s.summary = %q{Xen API XMLRPC Client}
|
40
|
+
|
41
|
+
if s.respond_to? :specification_version then
|
42
|
+
s.specification_version = 3
|
43
|
+
|
44
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
45
|
+
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
46
|
+
else
|
47
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
48
|
+
end
|
49
|
+
else
|
50
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xenapi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 7
|
10
|
+
version: 0.2.7
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Geoff Garside
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-09 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: thoughtbot-shoulda
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
description: Xen API XMLRPC Client library for working with XenServers
|
35
|
+
email: geoff+xenapi@geoffgarside.co.uk
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- LICENSE
|
42
|
+
- README.rdoc
|
43
|
+
files:
|
44
|
+
- .yardopts
|
45
|
+
- HISTORY
|
46
|
+
- LICENSE
|
47
|
+
- README.rdoc
|
48
|
+
- Rakefile
|
49
|
+
- VERSION
|
50
|
+
- lib/xen_api.rb
|
51
|
+
- lib/xenapi.rb
|
52
|
+
- lib/xenapi/async_dispatcher.rb
|
53
|
+
- lib/xenapi/client.rb
|
54
|
+
- lib/xenapi/dispatcher.rb
|
55
|
+
- lib/xenapi/errors.rb
|
56
|
+
- test/helper.rb
|
57
|
+
- test/test_xenapi.rb
|
58
|
+
- xenapi.gemspec
|
59
|
+
homepage: http://github.com/geoffgarside/xenapi
|
60
|
+
licenses: []
|
61
|
+
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
hash: 3
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
requirements: []
|
86
|
+
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.7.2
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Xen API XMLRPC Client
|
92
|
+
test_files: []
|
93
|
+
|