xenapi-ruby 0.1.29
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/Gemfile +2 -0
- data/Gemfile.lock +39 -0
- data/LICENSE +20 -0
- data/README.rdoc +26 -0
- data/Rakefile +8 -0
- data/VERSION +1 -0
- data/config/xenapi.yml +4 -0
- data/config/xenapi.yml.example +4 -0
- data/lib/xenapi-ruby.rb +1 -0
- data/lib/xenapi.rb +12 -0
- data/lib/xenapi/dispatcher.rb +48 -0
- data/lib/xenapi/error.rb +71 -0
- data/lib/xenapi/hypervisor_connection.rb +46 -0
- data/lib/xenapi/network.rb +8 -0
- data/lib/xenapi/session.rb +61 -0
- data/lib/xenapi/storage.rb +24 -0
- data/lib/xenapi/task.rb +16 -0
- data/lib/xenapi/vbd.rb +20 -0
- data/lib/xenapi/vdi.rb +54 -0
- data/lib/xenapi/version.rb +8 -0
- data/lib/xenapi/virtual_machine.rb +211 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/xen_api_mockery.rb +29 -0
- data/spec/xenapi/dispatcher_spec.rb +65 -0
- data/spec/xenapi/error_spec.rb +85 -0
- data/spec/xenapi/network_spec.rb +14 -0
- data/spec/xenapi/session_spec.rb +83 -0
- data/spec/xenapi/storage_spec.rb +28 -0
- data/spec/xenapi/task_spec.rb +22 -0
- data/spec/xenapi/vbd_spec.rb +12 -0
- data/spec/xenapi/vdi_spec.rb +46 -0
- data/spec/xenapi/virtual_machine_spec.rb +32 -0
- data/xenapi-ruby.gemspec +20 -0
- metadata +100 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
xenapi-ruby (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
archive-tar-minitar (0.5.2)
|
10
|
+
columnize (0.3.4)
|
11
|
+
diff-lcs (1.1.3)
|
12
|
+
linecache19 (0.5.12)
|
13
|
+
ruby_core_source (>= 0.1.4)
|
14
|
+
rspec (2.7.0)
|
15
|
+
rspec-core (~> 2.7.0)
|
16
|
+
rspec-expectations (~> 2.7.0)
|
17
|
+
rspec-mocks (~> 2.7.0)
|
18
|
+
rspec-core (2.7.1)
|
19
|
+
rspec-expectations (2.7.0)
|
20
|
+
diff-lcs (~> 1.1.2)
|
21
|
+
rspec-mocks (2.7.0)
|
22
|
+
ruby-debug-base19 (0.11.25)
|
23
|
+
columnize (>= 0.3.1)
|
24
|
+
linecache19 (>= 0.5.11)
|
25
|
+
ruby_core_source (>= 0.1.4)
|
26
|
+
ruby-debug19 (0.11.6)
|
27
|
+
columnize (>= 0.3.1)
|
28
|
+
linecache19 (>= 0.5.11)
|
29
|
+
ruby-debug-base19 (>= 0.11.19)
|
30
|
+
ruby_core_source (0.1.5)
|
31
|
+
archive-tar-minitar (>= 0.5.2)
|
32
|
+
|
33
|
+
PLATFORMS
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
rspec
|
38
|
+
ruby-debug19
|
39
|
+
xenapi-ruby!
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Locaweb <development@locaweb.com.br>
|
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,26 @@
|
|
1
|
+
= xenapi-ruby
|
2
|
+
|
3
|
+
xenapi-ruby is a ruby gem to deal with xen api, executing commands acording to your xenserver version api documentation.
|
4
|
+
|
5
|
+
== Usage
|
6
|
+
|
7
|
+
Usage here
|
8
|
+
|
9
|
+
== Note on Patches/Pull Requests
|
10
|
+
|
11
|
+
* Fork the project.
|
12
|
+
* Make your feature addition or bug fix.
|
13
|
+
* Add tests for it. This is important so I don't break it in a
|
14
|
+
future version unintentionally.
|
15
|
+
* Commit, do not mess with rakefile, version, or history.
|
16
|
+
(if you want to have your own version, that is fine but
|
17
|
+
bump version in a commit by itself I can ignore when I pull)
|
18
|
+
* Send me a pull request.
|
19
|
+
|
20
|
+
== Contributors
|
21
|
+
|
22
|
+
* Fabio Kung
|
23
|
+
|
24
|
+
== Copyright
|
25
|
+
|
26
|
+
Copyright (c) 2009 Locaweb <development@locaweb.com>. See LICENSE for details.
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.4
|
data/config/xenapi.yml
ADDED
data/lib/xenapi-ruby.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "xenapi"
|
data/lib/xenapi.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require "yaml"
|
3
|
+
require "xenapi/virtual_machine"
|
4
|
+
require "xenapi/storage"
|
5
|
+
require "xenapi/vdi"
|
6
|
+
require "xenapi/vbd"
|
7
|
+
require "xenapi/task"
|
8
|
+
require "xenapi/network"
|
9
|
+
|
10
|
+
require "xenapi/session"
|
11
|
+
require "xenapi/error"
|
12
|
+
require "xenapi/hypervisor_connection"
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module XenAPI
|
2
|
+
class Dispatcher
|
3
|
+
undef_method :clone
|
4
|
+
|
5
|
+
def initialize(proxy, &error_callback)
|
6
|
+
@proxy = adjust_proxy_methods!(proxy)
|
7
|
+
@error_callback = error_callback
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(name, *args)
|
11
|
+
begin
|
12
|
+
response = @proxy.send(name, *args)
|
13
|
+
raise XenAPI::ErrorFactory.create(*response['ErrorDescription']) unless response['Status'] == 'Success'
|
14
|
+
response['Value']
|
15
|
+
rescue Exception => exc
|
16
|
+
error = XenAPI::ErrorFactory.wrap(exc)
|
17
|
+
if @error_callback
|
18
|
+
@error_callback.call(error) do |new_session|
|
19
|
+
prefix = @proxy.prefix.delete(".").to_sym
|
20
|
+
dispatcher = new_session.send(prefix)
|
21
|
+
dispatcher.send(name, *args)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
raise error
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def adjust_proxy_methods!(proxy)
|
31
|
+
proxy.instance_eval do
|
32
|
+
def clone(*args)
|
33
|
+
method_missing(:clone, *args)
|
34
|
+
end
|
35
|
+
def copy(*args)
|
36
|
+
method_missing(:copy, *args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def prefix
|
40
|
+
@prefix
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
proxy
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
data/lib/xenapi/error.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module XenAPI
|
4
|
+
|
5
|
+
class Error < RuntimeError
|
6
|
+
end
|
7
|
+
|
8
|
+
class ConnectionError < Error
|
9
|
+
end
|
10
|
+
|
11
|
+
class AuthenticationError < Error
|
12
|
+
end
|
13
|
+
|
14
|
+
class ExpirationError < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
class TimeoutError < Error
|
18
|
+
end
|
19
|
+
|
20
|
+
class UnauthenticatedClient < Error
|
21
|
+
def initialize(message = "Client needs to be authenticated first")
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class NotMasterError < Error
|
27
|
+
attr_accessor :master_ip
|
28
|
+
|
29
|
+
def initialize(message)
|
30
|
+
super
|
31
|
+
@master_ip = (/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/).match(message)[0]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module ErrorFactory
|
36
|
+
|
37
|
+
API_ERRORS = {
|
38
|
+
"SESSION_AUTHENTICATION_FAILED" => AuthenticationError,
|
39
|
+
"HOST_IS_SLAVE" => NotMasterError,
|
40
|
+
"HOST_STILL_BOOTING" => ConnectionError
|
41
|
+
}
|
42
|
+
|
43
|
+
TRANSLATIONS = {
|
44
|
+
EOFError => ExpirationError,
|
45
|
+
OpenSSL::SSL::SSLError => ExpirationError,
|
46
|
+
Errno::EHOSTUNREACH => ConnectionError,
|
47
|
+
Errno::ECONNREFUSED => ConnectionError,
|
48
|
+
Errno::EPIPE => ConnectionError,
|
49
|
+
Timeout::Error => TimeoutError
|
50
|
+
}
|
51
|
+
|
52
|
+
def create(*args)
|
53
|
+
key = args[0]
|
54
|
+
message = args.join(": ")
|
55
|
+
error_class = API_ERRORS[key]
|
56
|
+
|
57
|
+
return error_class.new message if error_class
|
58
|
+
Error.new message
|
59
|
+
end
|
60
|
+
|
61
|
+
def wrap(error)
|
62
|
+
return error if error.is_a? Error
|
63
|
+
|
64
|
+
error_class = TRANSLATIONS[error.class]
|
65
|
+
return error_class.new error.to_s if error_class
|
66
|
+
Error.new "<#{error.class}> #{error}"
|
67
|
+
end
|
68
|
+
|
69
|
+
module_function :create, :wrap
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
module XenAPI
|
3
|
+
module HypervisorConnection
|
4
|
+
class << self
|
5
|
+
def hypervisor_session(host)
|
6
|
+
sessions[host.ip] ||= hypervisor_session!(host)
|
7
|
+
end
|
8
|
+
|
9
|
+
def hypervisor_session!(host)
|
10
|
+
Rails.logger.info "Connecting to hypervisor on #{host.ip} with user #{host.username}"
|
11
|
+
session = SessionFactory.create(host.ip, host.username, host.password) do |error, &called_method|
|
12
|
+
Rails.logger.error(error)
|
13
|
+
session = HypervisorErrorHandler.new(host.pool, error).handle_error
|
14
|
+
|
15
|
+
if called_method
|
16
|
+
called_method.call(session)
|
17
|
+
else
|
18
|
+
session
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def close_hypervisor_session!
|
24
|
+
sessions.each do |host, session|
|
25
|
+
begin
|
26
|
+
session.close unless session.nil?
|
27
|
+
rescue Exception => error
|
28
|
+
Rails.logger.error("Error while trying to close connection to #{host}")
|
29
|
+
Rails.logger.error(error)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@sessions = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def sessions
|
37
|
+
@sessions ||= {}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def hypervisor_session
|
42
|
+
raise "Pool not found for #{self.inspect}" unless self.pool
|
43
|
+
HypervisorConnection.hypervisor_session(self.pool.master)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module XenAPI
|
2
|
+
require "xmlrpc/client"
|
3
|
+
require 'xenapi/dispatcher'
|
4
|
+
|
5
|
+
class Session
|
6
|
+
include XenAPI::VirtualMachine
|
7
|
+
include XenAPI::Vdi
|
8
|
+
include XenAPI::Vbd
|
9
|
+
include XenAPI::Storage
|
10
|
+
include XenAPI::Task
|
11
|
+
include XenAPI::Network
|
12
|
+
|
13
|
+
attr_reader :key
|
14
|
+
|
15
|
+
def initialize(uri, &block)
|
16
|
+
@uri, @block = uri, block
|
17
|
+
end
|
18
|
+
|
19
|
+
def login_with_password(username, password, timeout = 1200)
|
20
|
+
begin
|
21
|
+
@client = XMLRPC::Client.new2(@uri, nil, timeout)
|
22
|
+
@session = @client.proxy("session")
|
23
|
+
|
24
|
+
response = @session.login_with_password(username, password)
|
25
|
+
raise XenAPI::ErrorFactory.create(*response['ErrorDescription']) unless response['Status'] == 'Success'
|
26
|
+
|
27
|
+
@key = response["Value"]
|
28
|
+
|
29
|
+
#Let's check if it is a working master. It's a small pog due to xen not working as we would like
|
30
|
+
self.pool.get_all
|
31
|
+
|
32
|
+
self
|
33
|
+
rescue Exception => exc
|
34
|
+
error = XenAPI::ErrorFactory.wrap(exc)
|
35
|
+
if @block
|
36
|
+
# returns a new session
|
37
|
+
@block.call(error)
|
38
|
+
else
|
39
|
+
raise error
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def close
|
45
|
+
@session.logout(@key)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Avoiding method missing to get lost with Rake Task
|
49
|
+
# (considering Xen tasks as Rake task (???)
|
50
|
+
def task(*args)
|
51
|
+
method_missing("task", *args)
|
52
|
+
end
|
53
|
+
|
54
|
+
def method_missing(name, *args)
|
55
|
+
raise XenAPI::UnauthenticatedClient.new unless @key
|
56
|
+
|
57
|
+
proxy = @client.proxy(name.to_s, @key, *args)
|
58
|
+
Dispatcher.new(proxy, &@block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module XenAPI
|
3
|
+
module Storage
|
4
|
+
def storage_ref(uuid)
|
5
|
+
self.SR.get_by_uuid(uuid)
|
6
|
+
end
|
7
|
+
|
8
|
+
def storage_record(ref)
|
9
|
+
self.SR.get_record(ref)
|
10
|
+
end
|
11
|
+
|
12
|
+
def storage_record_by_uuid(uuid)
|
13
|
+
self.SR.get_record(self.SR.get_by_uuid(uuid))
|
14
|
+
end
|
15
|
+
|
16
|
+
def storage_name(uuid)
|
17
|
+
self.SR.get_name_label storage_ref(uuid)
|
18
|
+
end
|
19
|
+
|
20
|
+
def all_storages
|
21
|
+
self.SR.get_all
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/xenapi/task.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module XenAPI
|
3
|
+
module Task
|
4
|
+
def task_record(ref)
|
5
|
+
self.task.get_record ref
|
6
|
+
end
|
7
|
+
|
8
|
+
def task_destroy(ref)
|
9
|
+
self.task.destroy ref
|
10
|
+
end
|
11
|
+
|
12
|
+
def task_create(name, description="Task creation")
|
13
|
+
self.task.create(name, description)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/xenapi/vbd.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module XenAPI
|
3
|
+
module Vbd
|
4
|
+
def create_VBD_for(vm_ref, disk_uuid, disk_number)
|
5
|
+
self.VBD.create({
|
6
|
+
:VM => vm_ref,
|
7
|
+
:VDI => self.VDI.get_by_uuid(disk_uuid),
|
8
|
+
:userdevice => disk_number.to_s,
|
9
|
+
:bootable => false,
|
10
|
+
:mode => "RW",
|
11
|
+
:type => "Disk",
|
12
|
+
:unpluggable => false,
|
13
|
+
:empty => false,
|
14
|
+
:other_config => {},
|
15
|
+
:qos_algorithm_type => "",
|
16
|
+
:qos_algorithm_params => {}
|
17
|
+
})
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/xenapi/vdi.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module XenAPI
|
3
|
+
module Vdi
|
4
|
+
def create_VDI_for(storage_ref, vm_object, vdi_number)
|
5
|
+
vdi_ref = self.VDI.create({
|
6
|
+
:name_label => "#{vm_object.name} DISK #{vdi_number}",
|
7
|
+
:name_description => "#{vm_object.name} DISK #{vdi_number}",
|
8
|
+
:SR => storage_ref,
|
9
|
+
:virtual_size => (vm_object.hdd - vm_object.hdd_size).gigabytes.to_s,
|
10
|
+
:type => "system",
|
11
|
+
:sharable => false,
|
12
|
+
:read_only => false,
|
13
|
+
:other_config => {},
|
14
|
+
:xenstore_data => {},
|
15
|
+
:sm_config => {},
|
16
|
+
:tags => []
|
17
|
+
})
|
18
|
+
|
19
|
+
self.VDI.get_record(vdi_ref)["uuid"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def vdi_ref(uuid)
|
23
|
+
self.VDI.get_by_uuid(uuid)
|
24
|
+
end
|
25
|
+
|
26
|
+
def vdi_record(ref)
|
27
|
+
self.VDI.get_record(ref)
|
28
|
+
end
|
29
|
+
|
30
|
+
def vdi_clone(ref)
|
31
|
+
self.VDI.clone(vdi_ref)
|
32
|
+
end
|
33
|
+
|
34
|
+
def vdi_virtual_size(ref)
|
35
|
+
self.VDI.get_virtual_size(ref).to_i/(1024**3)
|
36
|
+
end
|
37
|
+
|
38
|
+
def vdi_resize(ref, new_size)
|
39
|
+
self.VDI.resize(ref, new_size.to_s)
|
40
|
+
end
|
41
|
+
|
42
|
+
def vdi_name_label=(ref, label)
|
43
|
+
self.VDI.set_name_label(ref, label)
|
44
|
+
end
|
45
|
+
|
46
|
+
def vdi_name_label(label)
|
47
|
+
self.VDI.get_by_name_label(label).first
|
48
|
+
end
|
49
|
+
|
50
|
+
def vdi_name_description=(ref, label)
|
51
|
+
self.VDI.set_name_description(ref, label)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
module XenAPI
|
2
|
+
module VirtualMachine
|
3
|
+
def vm_ref(uuid)
|
4
|
+
self.VM.get_by_uuid(uuid)
|
5
|
+
end
|
6
|
+
|
7
|
+
def vm_record(ref)
|
8
|
+
self.VM.get_record(ref)
|
9
|
+
end
|
10
|
+
|
11
|
+
def vm_clone(ref_to_clone, name)
|
12
|
+
self.VM.clone(ref_to_clone, name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def tools_outdated?(ref)
|
16
|
+
guest_ref = self.VM.get_guest_metrics(ref)
|
17
|
+
guest_ref == "OpaqueRef:NULL" || !self.VM_guest_metrics.get_PV_drivers_up_to_date(guest_ref)
|
18
|
+
rescue Exception
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def vbds(ref, opts = {})
|
23
|
+
vm_vbds_refs = self.VM.get_record(ref)["VBDs"]
|
24
|
+
|
25
|
+
vm_vbds_refs.inject({}) do |disks, vm_vbd_ref|
|
26
|
+
vm_vbd_record = self.VBD.get_record(vm_vbd_ref)
|
27
|
+
|
28
|
+
disks[vm_vbd_ref] = vm_vbd_record if opts[:include_cd] || vm_vbd_record["type"] == "Disk"
|
29
|
+
disks
|
30
|
+
end
|
31
|
+
rescue => e
|
32
|
+
{}
|
33
|
+
end
|
34
|
+
|
35
|
+
def hdd_physical_utilisation(vm_ref)
|
36
|
+
vbds(vm_ref).values.inject(0) do |disk_size, vbd_record|
|
37
|
+
disk_size += self.VDI.get_physical_utilisation(vbd_record['VDI']).to_i
|
38
|
+
end / (1024**3) # in GB
|
39
|
+
end
|
40
|
+
|
41
|
+
def hdd_size(vm_ref)
|
42
|
+
vbds(vm_ref).values.inject(0) do |disk_size, vbd_record|
|
43
|
+
disk_size += self.VDI.get_virtual_size(vbd_record['VDI']).to_i
|
44
|
+
end / (1024**3) # in GB
|
45
|
+
end
|
46
|
+
|
47
|
+
def vm_main_vif_ref(ref)
|
48
|
+
self.VM.get_VIFs(ref).find do |vif_ref|
|
49
|
+
self.VIF.get_device(vif_ref) == "0"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def vifs(ref)
|
54
|
+
vif_refs = self.VM.get_VIFs(ref)
|
55
|
+
vif_refs.inject({}) do |interfaces, vif_ref|
|
56
|
+
network_ref = self.VIF.get_network(vif_ref)
|
57
|
+
network_label = self.network.get_name_label(network_ref)
|
58
|
+
interfaces[vif_ref] = self.VIF.get_record(vif_ref).merge("network_label" => network_label)
|
59
|
+
|
60
|
+
interfaces
|
61
|
+
end
|
62
|
+
rescue
|
63
|
+
{}
|
64
|
+
end
|
65
|
+
|
66
|
+
def remove_disks_from_hypervisor(vm_ref)
|
67
|
+
vbds(vm_ref).each_value do |vbd_record|
|
68
|
+
self.VDI.destroy(vbd_record['VDI'])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def next_disk_number(vm_ref)
|
73
|
+
device_map = {}
|
74
|
+
|
75
|
+
vbds(vm_ref, :include_cd => true).each_pair do |vbd_ref, vbd_record|
|
76
|
+
userdevice = vbd_record["userdevice"].to_i
|
77
|
+
device_map[userdevice] = vbd_ref
|
78
|
+
end
|
79
|
+
|
80
|
+
disk_number = 0
|
81
|
+
|
82
|
+
device_map.size.times do
|
83
|
+
break if device_map[disk_number].nil?
|
84
|
+
disk_number +=1
|
85
|
+
end
|
86
|
+
|
87
|
+
disk_number
|
88
|
+
end
|
89
|
+
|
90
|
+
def cd_ref(vm_ref)
|
91
|
+
vbds(vm_ref, :include_cd => true).each_pair do |ref, record|
|
92
|
+
return ref if record["type"] == "CD"
|
93
|
+
end
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def insert_iso_cd(cd_ref, iso_ref)
|
98
|
+
self.VBD.set_bootable(cd_ref, false)
|
99
|
+
self.VBD.insert(cd_ref, iso_ref)
|
100
|
+
true
|
101
|
+
end
|
102
|
+
|
103
|
+
def master_address
|
104
|
+
pool_ref = self.pool.get_all.first
|
105
|
+
master_ref = self.pool.get_master pool_ref
|
106
|
+
self.host.get_address master_ref
|
107
|
+
end
|
108
|
+
|
109
|
+
def configure_network_interfaces_on(vm_ref)
|
110
|
+
vif_refs = self.VM.get_VIFs(vm_ref)
|
111
|
+
raise "Template doesn't have any network interfaces" if vif_refs.nil? || vif_refs.empty?
|
112
|
+
vif_record = self.VIF.get_record(vm_main_vif_ref(vm_ref))
|
113
|
+
vif_record["MAC"]
|
114
|
+
end
|
115
|
+
|
116
|
+
def exists_on_hypervisor?(uuid)
|
117
|
+
self.VM.get_by_uuid(uuid)
|
118
|
+
true
|
119
|
+
rescue XenAPI::Error
|
120
|
+
false
|
121
|
+
end
|
122
|
+
|
123
|
+
def set_memory_size(vm_ref, memory_in_MB)
|
124
|
+
memory_in_MB = memory_in_MB.to_s
|
125
|
+
self.VM.set_memory_limits(vm_ref, memory_in_MB, memory_in_MB, memory_in_MB, memory_in_MB)
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
def created_vifs(vm_ref)
|
130
|
+
vifs(vm_ref).to_a.inject({}) do |map, pair|
|
131
|
+
map[pair.last["network_label"]] = pair.first
|
132
|
+
map
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def set_cpus_size(vm_ref, cpus)
|
137
|
+
cpus = cpus.to_s
|
138
|
+
max_cpus = self.VM.get_VCPUs_max(vm_ref).to_i
|
139
|
+
|
140
|
+
# On upgrade, we want set VCPUS max before VCPUS at startup and vice versa
|
141
|
+
if cpus.to_i > max_cpus
|
142
|
+
self.VM.set_VCPUs_max(vm_ref, cpus)
|
143
|
+
self.VM.set_VCPUs_at_startup(vm_ref, cpus)
|
144
|
+
else
|
145
|
+
self.VM.set_VCPUs_at_startup(vm_ref, cpus)
|
146
|
+
self.VM.set_VCPUs_max(vm_ref, cpus)
|
147
|
+
end
|
148
|
+
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
152
|
+
def adjust_vcpu_settings(vm_ref, new_settings)
|
153
|
+
parameters = self.VM.get_VCPUs_params(vm_ref)
|
154
|
+
parameters.merge!(new_settings)
|
155
|
+
self.VM.set_VCPUs_params(vm_ref, parameters)
|
156
|
+
|
157
|
+
self
|
158
|
+
end
|
159
|
+
|
160
|
+
def import(file_path, storage_uuid = nil)
|
161
|
+
file = File.open(file_path, "rb")
|
162
|
+
|
163
|
+
session_ref = self.key
|
164
|
+
storage_ref = storage_uuid ? self.SR.get_by_uuid(storage_uuid) : nil
|
165
|
+
task_ref = self.task.create "import vm #{file_path}", "importat job"
|
166
|
+
|
167
|
+
path = "/import?session_id=#{session_ref}&task_id=#{task_ref}"
|
168
|
+
path += "&sr_id=#{storage_ref}" if storage_ref
|
169
|
+
|
170
|
+
http = Net::HTTP.new(master_address, 80)
|
171
|
+
request = Net::HTTP::Put.new(path, {})
|
172
|
+
request.body_stream = file
|
173
|
+
request.content_length = file.size
|
174
|
+
begin
|
175
|
+
http.request(request)
|
176
|
+
rescue Errno::ECONNRESET
|
177
|
+
Rails.logger.warn "VM import did a connection reset, but does not indicate an error"
|
178
|
+
rescue Timeout::Error
|
179
|
+
error = "Could not import VM due to timeout error: check if your image is valid"
|
180
|
+
Rails.logger.error error
|
181
|
+
raise error
|
182
|
+
end
|
183
|
+
|
184
|
+
task_rec = self.task.get_record task_ref
|
185
|
+
vm_ref = task_rec["result"].gsub(/<.*?>/, "")
|
186
|
+
self.VM.get_uuid(vm_ref)
|
187
|
+
ensure
|
188
|
+
file.close rescue nil
|
189
|
+
self.task.destroy(task_ref) rescue nil
|
190
|
+
end
|
191
|
+
|
192
|
+
def export(vm_uuid, options = {})
|
193
|
+
options = {:to => "/tmp/export_file"}.merge(options)
|
194
|
+
file = File.open(options[:to], "wb")
|
195
|
+
session_ref = self.key
|
196
|
+
task_ref = self.task.create "export vm #{vm_uuid}", "export job"
|
197
|
+
|
198
|
+
path = "/export?session_id=#{session_ref}&task_id=#{task_ref}&ref=#{self.vm_ref(vm_uuid)}"
|
199
|
+
uri = URI.parse "http://#{master_address}#{path}"
|
200
|
+
|
201
|
+
Net::HTTP.get_response(uri) do |res|
|
202
|
+
res.read_body {|chunk| file.write chunk }
|
203
|
+
end
|
204
|
+
|
205
|
+
options[:to]
|
206
|
+
ensure
|
207
|
+
file.close rescue nil
|
208
|
+
self.task.destroy(task_ref) rescue nil
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
|
5
|
+
require 'xenapi-ruby'
|
6
|
+
|
7
|
+
::OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:verify_mode] = ::OpenSSL::SSL::VERIFY_NONE
|
8
|
+
|
9
|
+
# Requiring all support files
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.include(Mock::XenAPIMockery)
|
14
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
module Mock
|
3
|
+
module XenAPIMockery
|
4
|
+
def mock_session
|
5
|
+
SessionMock.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def success_response
|
9
|
+
{'Status' => 'Success', 'Value' => ''}
|
10
|
+
end
|
11
|
+
|
12
|
+
class SessionMock
|
13
|
+
def initialize
|
14
|
+
@mocks = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def copy; end
|
18
|
+
|
19
|
+
def method_missing(name, *args)
|
20
|
+
unless @mocks.has_key?(name)
|
21
|
+
@mocks[name] = SessionMock.new
|
22
|
+
self.stub(name).and_return @mocks[name]
|
23
|
+
end
|
24
|
+
|
25
|
+
@mocks[name]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
3
|
+
|
4
|
+
describe XenAPI::Dispatcher do
|
5
|
+
FAKE_ERROR_MESSAGE = 'ERROR'
|
6
|
+
FAKE_ERROR_DETAIL = 'WRONG METHOD'
|
7
|
+
|
8
|
+
class FakeProxy
|
9
|
+
def initialize
|
10
|
+
@prefix = 'vm.'
|
11
|
+
end
|
12
|
+
|
13
|
+
def successful_call
|
14
|
+
{'Status' => 'Success', 'Value' => :success}
|
15
|
+
end
|
16
|
+
|
17
|
+
def unsuccessful_call
|
18
|
+
{'Status' => 'Failure', 'Value' => :failure, 'ErrorDescription' => [FAKE_ERROR_MESSAGE, FAKE_ERROR_DETAIL]}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
subject { XenAPI::Dispatcher.new(FakeProxy.new) }
|
23
|
+
|
24
|
+
it "should forward any call to clone to method missing" do
|
25
|
+
subject.should_receive(:method_missing).with(:clone)
|
26
|
+
subject.clone
|
27
|
+
end
|
28
|
+
it "should forward any call to copy to method missing" do
|
29
|
+
subject.should_receive(:method_missing).with(:copy)
|
30
|
+
subject.copy
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when sucessfully forwarding calls to the proxy" do
|
34
|
+
it "should return 'value' from the response hash" do
|
35
|
+
subject.successful_call.should == :success
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when failing to forward calls to the proxy" do
|
40
|
+
it "should raise an error with the message returned by the result's 'ErrorDescription'" do
|
41
|
+
expect{ subject.unsuccessful_call }.to raise_error(Exception, "#{FAKE_ERROR_MESSAGE}: #{FAKE_ERROR_DETAIL}")
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with a callback" do
|
45
|
+
before do
|
46
|
+
@dispatcher = subject.class.new(FakeProxy.new) do |error, &original_call|
|
47
|
+
rpc_proxy = mock(:rpc)
|
48
|
+
rpc_proxy.should_receive(:retry_method)
|
49
|
+
new_session = mock(:session)
|
50
|
+
new_session.should_receive(:send).with(:vm).and_return(rpc_proxy)
|
51
|
+
original_call.call(new_session)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should call the callback passed as a block to the initializer and not raise an error" do
|
56
|
+
@dispatcher.instance_variable_get(:@error_callback).should_receive(:call)
|
57
|
+
lambda{@dispatcher.unsuccessful_call}.should_not raise_error
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should create a new session and retry to call the missing method on the new session proxy" do
|
61
|
+
@dispatcher.retry_method
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
3
|
+
|
4
|
+
describe XenAPI::Error do
|
5
|
+
context "when dealing with Xen API errors" do
|
6
|
+
it "should generate an XenAPI::AuthenticationError object for authentication error" do
|
7
|
+
error_description = "SESSION_AUTHENTICATION_FAILED"
|
8
|
+
error = XenAPI::ErrorFactory.create(*error_description)
|
9
|
+
error.should be_instance_of(XenAPI::AuthenticationError)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should generate an XenAPI::ConnectionError object for host still booting error" do
|
13
|
+
error_description = "HOST_STILL_BOOTING"
|
14
|
+
error = XenAPI::ErrorFactory.create(*error_description)
|
15
|
+
error.should be_instance_of(XenAPI::ConnectionError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should generate an XenAPI::Error object for unknown error" do
|
19
|
+
error_description = "UNKNWON_STATE"
|
20
|
+
error = XenAPI::ErrorFactory.create(*error_description)
|
21
|
+
error.should be_instance_of(XenAPI::Error)
|
22
|
+
end
|
23
|
+
|
24
|
+
context "for not master error" do
|
25
|
+
before do
|
26
|
+
error_description = ["HOST_IS_SLAVE", "10.11.0.11"]
|
27
|
+
@error = XenAPI::ErrorFactory.create(*error_description)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should generate the right error" do
|
31
|
+
@error.should be_instance_of(XenAPI::NotMasterError)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should parse not master error message and return master ip" do
|
35
|
+
@error.master_ip.should == "10.11.0.11"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when selecting errors, it should generate a specific error message" do
|
41
|
+
it "for expiration error" do
|
42
|
+
expiration_error = EOFError.new "end of file reached"
|
43
|
+
error = XenAPI::ErrorFactory.wrap(expiration_error)
|
44
|
+
error.should be_instance_of(XenAPI::ExpirationError)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "for timeout error" do
|
48
|
+
expiration_error = Timeout::Error.new "execution expired"
|
49
|
+
error = XenAPI::ErrorFactory.wrap(expiration_error)
|
50
|
+
error.should be_instance_of(XenAPI::TimeoutError)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "for XenAPI::Error instance" do
|
54
|
+
xenapi_error = XenAPI::UnauthenticatedClient.new
|
55
|
+
error = XenAPI::ErrorFactory.wrap(xenapi_error)
|
56
|
+
error.should === xenapi_error
|
57
|
+
end
|
58
|
+
|
59
|
+
context "for connection error" do
|
60
|
+
it "when host is unreachable" do
|
61
|
+
connection_error = Errno::EHOSTUNREACH.new "No route to host"
|
62
|
+
error = XenAPI::ErrorFactory.wrap(connection_error)
|
63
|
+
error.should be_instance_of(XenAPI::ConnectionError)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "when connection is refused" do
|
67
|
+
connection_error = Errno::ECONNREFUSED.new "Connection refused – connect(2)"
|
68
|
+
error = XenAPI::ErrorFactory.wrap(connection_error)
|
69
|
+
error.should be_instance_of(XenAPI::ConnectionError)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "when there is a broken pipe" do
|
73
|
+
connection_error = Errno::EPIPE.new "Broken pipe"
|
74
|
+
error = XenAPI::ErrorFactory.wrap(connection_error)
|
75
|
+
error.should be_instance_of(XenAPI::ConnectionError)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "for generic error" do
|
80
|
+
generic_error = RuntimeError.new "Generic Error"
|
81
|
+
error = XenAPI::ErrorFactory.wrap(generic_error)
|
82
|
+
error.should be_instance_of(XenAPI::Error)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
3
|
+
|
4
|
+
class A; include XenAPI::Network; end
|
5
|
+
|
6
|
+
describe XenAPI::Network do
|
7
|
+
subject { A.new }
|
8
|
+
|
9
|
+
pending "Write real tests"
|
10
|
+
|
11
|
+
it "should contain the network_name_label method" do
|
12
|
+
subject.should respond_to(:task_create)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
3
|
+
|
4
|
+
describe XenAPI::Session do
|
5
|
+
before(:all) do
|
6
|
+
@configs = YAML.load_file(File.dirname(__FILE__) + '/../../config/xenapi.yml')["xenapi"]
|
7
|
+
end
|
8
|
+
|
9
|
+
subject { XenAPI::Session.new(@configs["xenurl"]) }
|
10
|
+
|
11
|
+
it "should process method_missing only if client is already authenticated" do
|
12
|
+
expect{ subject.invalid_method }.to raise_error(XenAPI::UnauthenticatedClient)
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when generating a proxy" do
|
16
|
+
it "should undefine its copy method"
|
17
|
+
it "should undefine its clone method"
|
18
|
+
it "should add a prefix method to it"
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "without custom block" do
|
22
|
+
before do
|
23
|
+
subject.login_with_password(@configs["user"], @configs["password"])
|
24
|
+
end
|
25
|
+
|
26
|
+
after do
|
27
|
+
subject.close
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should list VMs" do
|
31
|
+
response = subject.VM.get_all
|
32
|
+
response.should_not be_nil
|
33
|
+
response.should respond_to("[]", "each")
|
34
|
+
response.first.should =~ /^OpaqueRef:.*$/
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should raise an error for invalid call by default" do
|
38
|
+
code_block = lambda {subject.VM.get_all("arg that makes test to raise an error")}
|
39
|
+
code_block.should raise_error(XenAPI::Error)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should raise an error if unable to login" do
|
43
|
+
lambda { subject.login_with_password('','') }.should raise_error
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "with custom block" do
|
48
|
+
subject do
|
49
|
+
XenAPI::Session.new(@configs["xenurl"]) do |error, &called_method|
|
50
|
+
@called_method = called_method
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
before(:each) do
|
55
|
+
subject.login_with_password(@configs["user"], @configs["password"])
|
56
|
+
end
|
57
|
+
|
58
|
+
after(:each) do
|
59
|
+
subject.close
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should not call block if no error happens" do
|
63
|
+
subject.VM.get_all
|
64
|
+
@called_method.should be_nil
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should call block if any xen error happens" do
|
68
|
+
subject.VM.get_all("arg that makes test to raise an error")
|
69
|
+
@called_method.should be_a(Proc)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "when no xen api is up" do
|
74
|
+
it "should call block if can't connect" do
|
75
|
+
@session = XenAPI::Session.new("https://169.167.161.160") do |error|
|
76
|
+
@method_called = true
|
77
|
+
end
|
78
|
+
|
79
|
+
@session.login_with_password(@configs["user"], @configs["password"], 1)
|
80
|
+
@method_called.should be_true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
3
|
+
|
4
|
+
class A; include XenAPI::Storage; end
|
5
|
+
|
6
|
+
describe XenAPI::Storage do
|
7
|
+
subject { A.new }
|
8
|
+
|
9
|
+
it "should contain the storage_ref method" do
|
10
|
+
subject.should respond_to(:storage_ref)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should contain the storage_record_by_uuid method" do
|
14
|
+
subject.should respond_to(:storage_record_by_uuid)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should contain the storage_record method" do
|
18
|
+
subject.should respond_to(:storage_record)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should contain the storage_name method" do
|
22
|
+
subject.should respond_to(:storage_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should contain the all_storages method" do
|
26
|
+
subject.should respond_to(:all_storages)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
3
|
+
|
4
|
+
class A; include XenAPI::Task; end
|
5
|
+
|
6
|
+
describe XenAPI::Task do
|
7
|
+
subject { A.new }
|
8
|
+
|
9
|
+
pending "Write real tests"
|
10
|
+
|
11
|
+
it "should contain the task_record method" do
|
12
|
+
subject.should respond_to(:task_record)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should contain the task_destroy method" do
|
16
|
+
subject.should respond_to(:task_destroy)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should contain the task_create method" do
|
20
|
+
subject.should respond_to(:task_create)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
3
|
+
|
4
|
+
class A; include XenAPI::Vbd; end
|
5
|
+
|
6
|
+
describe XenAPI::Vbd do
|
7
|
+
subject { A.new }
|
8
|
+
|
9
|
+
it "should contain the create_for method" do
|
10
|
+
subject.should respond_to(:create_VBD_for)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
3
|
+
|
4
|
+
class A; include XenAPI::Vdi; end
|
5
|
+
|
6
|
+
describe XenAPI::Vdi do
|
7
|
+
subject { A.new }
|
8
|
+
|
9
|
+
pending "Write real tests"
|
10
|
+
|
11
|
+
it "should contain the create_for method" do
|
12
|
+
subject.should respond_to(:create_VDI_for)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should contain the vdi_ref method" do
|
16
|
+
subject.should respond_to(:vdi_ref)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should contain the vdi_record method" do
|
20
|
+
subject.should respond_to(:vdi_record)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should contain the vdi_clone method" do
|
24
|
+
subject.should respond_to(:vdi_clone)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should contain the vdi_virtual_size method" do
|
28
|
+
subject.should respond_to(:vdi_virtual_size)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should contain the vdi_resize method" do
|
32
|
+
subject.should respond_to(:vdi_resize)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should contain the vdi_name_label method" do
|
36
|
+
subject.should respond_to(:vdi_name_label)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should contain the vdi_name_label= method" do
|
40
|
+
subject.should respond_to(:vdi_name_label=)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should contain the vdi_name_description= method" do
|
44
|
+
subject.should respond_to(:vdi_name_description=)
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
3
|
+
|
4
|
+
class A; include XenAPI::VirtualMachine; end
|
5
|
+
|
6
|
+
describe XenAPI::VirtualMachine do
|
7
|
+
subject { A.new }
|
8
|
+
|
9
|
+
before { subject.stub(:VM).and_return(@hypervisor_session = mock) }
|
10
|
+
|
11
|
+
it "should call the xen clone method" do
|
12
|
+
@hypervisor_session.should_receive(:clone).with("OpaqueRef:...","TheName")
|
13
|
+
subject.vm_clone("OpaqueRef:...", "TheName")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should call the xen method to get the vm ref" do
|
17
|
+
@hypervisor_session.should_receive(:get_by_uuid).with("262a2a64-3128-9e1a-1f05-49dc7012bb2c")
|
18
|
+
subject.vm_ref("262a2a64-3128-9e1a-1f05-49dc7012bb2c")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should call the xen method to get the vm record" do
|
22
|
+
@hypervisor_session.should_receive(:get_record).with("OpaqueRef:...")
|
23
|
+
subject.vm_record("OpaqueRef:...")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should adjust the vcpu settings for a given hash" do
|
27
|
+
settings = {"weight" => "100", "cap" => "200"}
|
28
|
+
@hypervisor_session.stub(:get_VCPUs_params).and_return({})
|
29
|
+
@hypervisor_session.should_receive(:set_VCPUs_params).with(anything, settings)
|
30
|
+
subject.adjust_vcpu_settings("ref", settings)
|
31
|
+
end
|
32
|
+
end
|
data/xenapi-ruby.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "xenapi/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "xenapi-ruby"
|
7
|
+
s.version = XenAPI::Version::STRING
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Cloud Team"]
|
10
|
+
s.email = ["willian.molinari@locaweb.com.br"]
|
11
|
+
s.homepage = "http://www.locaweb.com.br"
|
12
|
+
s.description = %Q{A simple gem to deal with Xen API}
|
13
|
+
s.summary = s.description
|
14
|
+
|
15
|
+
s.files = Dir["./**/*"].reject {|file| file =~ /\.git|pkg/}
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_development_dependency "rspec"
|
19
|
+
s.add_development_dependency "ruby-debug19"
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xenapi-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.29
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Cloud Team
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &19513640 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *19513640
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ruby-debug19
|
27
|
+
requirement: &19512980 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *19512980
|
36
|
+
description: A simple gem to deal with Xen API
|
37
|
+
email:
|
38
|
+
- willian.molinari@locaweb.com.br
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- ./Gemfile.lock
|
44
|
+
- ./VERSION
|
45
|
+
- ./config/xenapi.yml
|
46
|
+
- ./config/xenapi.yml.example
|
47
|
+
- ./xenapi-ruby.gemspec
|
48
|
+
- ./README.rdoc
|
49
|
+
- ./LICENSE
|
50
|
+
- ./spec/support/xen_api_mockery.rb
|
51
|
+
- ./spec/spec_helper.rb
|
52
|
+
- ./spec/xenapi/virtual_machine_spec.rb
|
53
|
+
- ./spec/xenapi/network_spec.rb
|
54
|
+
- ./spec/xenapi/dispatcher_spec.rb
|
55
|
+
- ./spec/xenapi/task_spec.rb
|
56
|
+
- ./spec/xenapi/storage_spec.rb
|
57
|
+
- ./spec/xenapi/session_spec.rb
|
58
|
+
- ./spec/xenapi/error_spec.rb
|
59
|
+
- ./spec/xenapi/vbd_spec.rb
|
60
|
+
- ./spec/xenapi/vdi_spec.rb
|
61
|
+
- ./Gemfile
|
62
|
+
- ./Rakefile
|
63
|
+
- ./lib/xenapi.rb
|
64
|
+
- ./lib/xenapi-ruby.rb
|
65
|
+
- ./lib/xenapi/virtual_machine.rb
|
66
|
+
- ./lib/xenapi/version.rb
|
67
|
+
- ./lib/xenapi/hypervisor_connection.rb
|
68
|
+
- ./lib/xenapi/dispatcher.rb
|
69
|
+
- ./lib/xenapi/network.rb
|
70
|
+
- ./lib/xenapi/error.rb
|
71
|
+
- ./lib/xenapi/vbd.rb
|
72
|
+
- ./lib/xenapi/task.rb
|
73
|
+
- ./lib/xenapi/storage.rb
|
74
|
+
- ./lib/xenapi/session.rb
|
75
|
+
- ./lib/xenapi/vdi.rb
|
76
|
+
homepage: http://www.locaweb.com.br
|
77
|
+
licenses: []
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 1.8.16
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: A simple gem to deal with Xen API
|
100
|
+
test_files: []
|