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 ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -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.
@@ -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.
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rspec/core/rake_task"
5
+ RSpec::Core::RakeTask.new
6
+
7
+ require 'cucumber/rake/task'
8
+ Cucumber::Rake::Task.new(:features)
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.4
@@ -0,0 +1,4 @@
1
+ xenapi:
2
+ xenurl: "https://10.20.64.3"
3
+ user: "root"
4
+ password: "lerolero"
@@ -0,0 +1,4 @@
1
+ xenapi:
2
+ xenurl: "https://127.0.0.1"
3
+ user: "root"
4
+ password: "My4w3s0m3P455w0rd"
@@ -0,0 +1 @@
1
+ require "xenapi"
@@ -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
@@ -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,8 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module XenAPI
3
+ module Network
4
+ def network_name_label(name_label)
5
+ self.network.get_by_name_label(name_label)
6
+ end
7
+ end
8
+ 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
@@ -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
@@ -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
@@ -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,8 @@
1
+ module XenAPI
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 29
6
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
+ end
8
+ 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
@@ -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
@@ -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: []