xenapi-ruby 0.1.29

Sign up to get free protection for your applications and to get access to all the features.
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: []