xencap 1.0.0

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.
@@ -0,0 +1,28 @@
1
+ require 'xencap/xenapi'
2
+
3
+ module Xencap
4
+ module Plugin
5
+ attr_reader :session, :request_dispatcher
6
+
7
+ def setup(uri, options = {})
8
+ @session = XenAPI::Session.new(uri)
9
+ _ignore_ssl_errors if options.fetch(:ignore_ssl_errors, false)
10
+
11
+ @session.login_with_password(options.fetch(:login), options.fetch(:password))
12
+ @request_dispatcher = Xencap::RequestDispatcher.new(@session)
13
+ end
14
+
15
+ def teardown
16
+ @session.logout unless @session.nil?
17
+ end
18
+
19
+ def method_missing(method, *args)
20
+ @request_dispatcher.dispatch(method, *args)
21
+ end
22
+
23
+ def _ignore_ssl_errors
24
+ # Allow self-signed certs
25
+ @session.instance_variable_get(:@http).instance_variable_set(:@verify_mode, OpenSSL::SSL::VERIFY_NONE)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ class Xencap::RequestDispatcher
2
+ def initialize(session)
3
+ @session = session
4
+ @proxy_classes = Dir[File.expand_path("../session_proxy/*rb", __FILE__)]
5
+ @proxies = {}
6
+
7
+ end
8
+
9
+ def dispatch(scope, *args)
10
+ if @proxies.has_key?(scope)
11
+ # nothing to do here
12
+ elsif filename = @proxy_classes.detect {|file| File.basename(file, ".rb").downcase == scope.to_s}
13
+ require filename
14
+ @proxies[scope] = Xencap::SessionProxy.const_get(scope.capitalize).new(@session)
15
+ else
16
+ @proxies[scope] = Xencap::SessionProxy.new(@session, scope)
17
+ end
18
+
19
+ @proxies[scope]
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ class Xencap::SessionProxy::Vm < Xencap::SessionProxy
2
+ def initialize(session)
3
+ @session = session
4
+ @scope = "VM"
5
+ end
6
+
7
+ def get_control_dom0
8
+ get_all_records.select do |vm|
9
+ vm['is_control_domain']
10
+ end
11
+ end
12
+
13
+ def get_templates
14
+ get_all_records.select do |vm|
15
+ vm['is_a_template']
16
+ end
17
+ end
18
+
19
+ def get_vms
20
+ get_all_records.reject do |vm|
21
+ vm['is_a_template'] || vm['is_control_domain']
22
+ end
23
+ end
24
+
25
+ def clone_template(template_name, name)
26
+ template_ref, template_record = get_all_records.detect do |ref, record|
27
+ record['is_a_template'] == true && record['name_label'] == template_name
28
+ end
29
+ ref = clone(template_ref, name)
30
+ set_is_a_template(ref, false)
31
+ ref
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ class Xencap::SessionProxy
2
+ def initialize(session, scope)
3
+ @session = session
4
+
5
+ if scope.length <= 3
6
+ @scope = scope.upcase
7
+ else
8
+ @scope = scope
9
+ end
10
+ end
11
+
12
+ def method_missing(method, *args, &block)
13
+ _request(method, *args)
14
+ end
15
+
16
+ def clone(*args)
17
+ if args.length > 0
18
+ _request(:clone, *args)
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def find_all_records(options = {})
25
+ get_all_records.select do |ref, record|
26
+ options.all? do |key, value|
27
+ record[key.to_s] == value
28
+ end
29
+ end
30
+ end
31
+
32
+ def find_record(options = {})
33
+ find_all_records(options).first
34
+ end
35
+
36
+ def _request(method, *args)
37
+ @session.send(@scope).send(method, *args)
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ Capistrano::Configuration.instance.load do
2
+ set :xencap_server_uri, nil
3
+ set :xencap_login, "root"
4
+ set :xencap_password, do
5
+ Capistrano::CLI.password_prompt("xen password: ")
6
+ end
7
+ set :xencap_ignore_ssl_errors, false
8
+
9
+ on :exit do
10
+ xencap.session.teardown
11
+ end
12
+
13
+ namespace :xencap do
14
+ namespace :session do
15
+ task :setup do
16
+ xencap_plugin.setup(
17
+ xencap_server_uri,
18
+ :login => xencap_login,
19
+ :password => xencap_password,
20
+ :ignore_ssl_errors => xencap_ignore_ssl_errors
21
+ )
22
+ end
23
+
24
+ task :teardown do
25
+ xencap_plugin.teardown
26
+ end
27
+ end
28
+
29
+ namespace :vm do
30
+ task :list do
31
+ xencap.session.setup
32
+ xencap_plugin.vm.get_all_records.reject {|ref, record| record['is_a_template'] || record['is_control_domain'] }.values.each {|v| puts v['name_label'] }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,232 @@
1
+ # ===========================================================================
2
+ # Copyright (c) 2010 Holger Just
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person
5
+ # obtaining a copy of this software and associated documentation
6
+ # files (the "Software"), to deal in the Software without
7
+ # restriction, including without limitation the rights to use,
8
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the
10
+ # Software is furnished to do so, subject to the following
11
+ # conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ # OTHER DEALINGS IN THE SOFTWARE.
24
+ #
25
+ # ===========================================================================
26
+ #
27
+ # This library is based on
28
+ # {XenAPI.py}[http://community.citrix.com/download/attachments/38633496/XenAPI.py]
29
+ # by XenSource Inc., licensed under the LGPL.
30
+ # ---------------------------------------------------------------------------
31
+ #
32
+ # ===========================================================================
33
+ # Grabbed on 2013-04-21 by plainlystated
34
+ # from: https://github.com/meineerde/xenapi.rb
35
+ # ---------------------------------------------------------------------------
36
+
37
+ require 'uri'
38
+ require 'xmlrpc/client'
39
+
40
+ module XenAPI
41
+ API_VERSION_1_1 = '1.1'
42
+ API_VERSION_1_2 = '1.2'
43
+
44
+ RETRY_COUNT = 3
45
+
46
+ class SessionInvalidError < Exception; end
47
+ class Failure < Exception
48
+ def initialize(details = [])
49
+ if details.is_a? Array
50
+ @error_type = details[0]
51
+ @error_details = details[1..-1] || []
52
+ else
53
+ @error_details = []
54
+ end
55
+ end
56
+
57
+ def to_s
58
+ details = case @error_details.length
59
+ when 0 then ""
60
+ when 1 then @error_details[0]
61
+ else @error_details.inspect
62
+ end
63
+
64
+ "#{@error_type.to_s}: #{details}"
65
+ end
66
+
67
+ attr_reader :error_type, :error_details
68
+ end
69
+
70
+ class Session < ::XMLRPC::Client
71
+ def initialize(uri, proxy_host=nil, proxy_port=nil)
72
+ # uri can be one of:
73
+ # * "http://server.name/path"
74
+ # * "https://server.name/path"
75
+ # * "socket:///var/xapi/xapi"
76
+ # proxy_host and proxy_port can be used to specify an HTTP proxy
77
+ @uri = URI.parse(uri)
78
+
79
+ case @uri.scheme.downcase
80
+ when 'http', 'https'
81
+ super(
82
+ @uri.host,
83
+ @uri.path.empty? ? "/" : @uri.path,
84
+ @uri.port,
85
+ proxy_host,
86
+ proxy_port,
87
+ nil, # user
88
+ nil, # password
89
+ (@uri.scheme.downcase == "https")
90
+ )
91
+ when 'socket'
92
+ raise NotImplementedError.new("Sockets are not supported yet. Sorry")
93
+ else
94
+ raise ArgumentError.new("Unknown scheme")
95
+ end
96
+
97
+ @api_version = API_VERSION_1_1
98
+ @session = ""
99
+ end
100
+
101
+ attr_reader :uri, :api_version
102
+ def session_id
103
+ @session
104
+ end
105
+
106
+ LOGIN_METHODS = %w(login_with_password slave_local_login_with_password)
107
+ LOGIN_METHODS.each do |method|
108
+ class_eval <<-"END_EVAL", __FILE__, __LINE__
109
+ def #{method}(*args)
110
+ begin
111
+ result = self.session.#{method}(*args)
112
+ rescue SessionInvalidError
113
+ raise ::XMLRPC::FaultException.new(500,
114
+ 'Received SESSION_INVALID when logging in')
115
+ end
116
+
117
+ @session = result
118
+ @last_login_method = :#{method}
119
+ @last_login_params = args
120
+ @api_version = _api_version
121
+ end
122
+ END_EVAL
123
+ end
124
+
125
+ def logout
126
+ # preferred method to logout the session
127
+ if @last_login_method.to_s.start_with?("slave_local")
128
+ self.session.local_logout
129
+ else
130
+ self.session.logout
131
+ end
132
+ end
133
+
134
+ def proxy(prefix=nil, *args)
135
+ # Overrides base method to use our custom Proxy class
136
+ XenAPIProxy.new(self, prefix, args, :call)
137
+ end
138
+
139
+ def proxy2(prefix=nil, *args)
140
+ # Overrides base method to use our custom Proxy class
141
+ XenAPIProxy.new(self, prefix, args, :call2)
142
+ end
143
+
144
+ def method_missing(sym, *args)
145
+ self.proxy(sym.to_s, *args)
146
+ end
147
+
148
+ private
149
+ def _api_version()
150
+ pool = self.pool.get_all()[0]
151
+ host = self.pool.get_master(pool)
152
+ major = self.host.get_API_version_major(host)
153
+ minor = self.host.get_API_version_minor(host)
154
+ "#{major}.#{minor}"
155
+ end
156
+
157
+ def _logout
158
+ # called from proxy object to release all session state
159
+ @session = ""
160
+ @last_login_method = nil
161
+ @last_login_params = nil
162
+ @api_version = API_VERSION_1_1
163
+ end
164
+ end
165
+
166
+ class XenAPIProxy < ::XMLRPC::Client::Proxy
167
+ def method_missing(method, *args, &block)
168
+ begin
169
+ if (@prefix == 'session.') && (Session::LOGIN_METHODS.include? method.to_s)
170
+ parse_result super(method, *args, &block)
171
+ else
172
+ retry_count = 0
173
+ while (retry_count < RETRY_COUNT)
174
+ session_args = [server(:session)] + args
175
+ begin
176
+ return parse_result super(method, *session_args, &block)
177
+ rescue SessionInvalidError
178
+ retry_count += 1
179
+ if server(:last_login_method)
180
+ @server.send(server(:last_login_method), *server(:last_login_params))
181
+ else
182
+ raise XMLRPC::FaultException.new(401, "You must log in")
183
+ end
184
+ end
185
+ end
186
+
187
+ raise ::XMLRPC::FaultException.new(500,
188
+ "Tried #{RETRY_COUNT} times to get a valid session, but failed")
189
+ end
190
+ ensure
191
+ # ensure we clear the global state on logout
192
+ @server.send(:_logout) if (@prefix == 'session.') && (method == :logout)
193
+ end
194
+ end
195
+
196
+ # method name clash between built-in clone and the method to clone a VM
197
+ def clone(*args)
198
+ args.length > 0 ? method_missing(:clone, *args) : super
199
+ end
200
+
201
+ private
202
+ def server(arg)
203
+ # returns an instance variable of the server
204
+ @server.instance_variable_get("@#{arg.to_s}")
205
+ end
206
+
207
+ def parse_result(result)
208
+ unless result.is_a?(Hash) && result.include?('Status')
209
+ raise XMLRPCClientError.new('Missing Status in response from server: ' + result.inspect)
210
+ end
211
+
212
+ if result['Status'] == 'Success'
213
+ if result.include? 'Value'
214
+ result['Value']
215
+ else
216
+ raise ::XMLRPC::FaultException.new('Missing Value in response from server')
217
+ end
218
+ else
219
+ if result.include? 'ErrorDescription'
220
+ if result['ErrorDescription'][0] == 'SESSION_INVALID'
221
+ raise SessionInvalidError
222
+ else
223
+ raise Failure.new(result['ErrorDescription'])
224
+ end
225
+ else
226
+ raise ::XMLRPC::FaultException.new('Missing ErrorDescription in response from server')
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+
data/lib/xencap.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Xencap
2
+ require File.expand_path('../xencap/tasks', __FILE__)
3
+
4
+ autoload :Plugin, File.expand_path('../xencap/plugin', __FILE__)
5
+ autoload :RequestDispatcher, File.expand_path('../xencap/request_dispatcher', __FILE__)
6
+ autoload :SessionProxy, File.expand_path('../xencap/session_proxy', __FILE__)
7
+ end
8
+
9
+ Capistrano.plugin :xencap_plugin, Xencap::Plugin
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xencap
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Patrick Schless
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-02-01 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Should work with any xen system that uses XAPI (including XenServer &
15
+ XCP)
16
+ email: patrick@plainlystated.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ./lib/xencap/plugin.rb
22
+ - ./lib/xencap/request_dispatcher.rb
23
+ - ./lib/xencap/session_proxy/vm.rb
24
+ - ./lib/xencap/session_proxy.rb
25
+ - ./lib/xencap/tasks.rb
26
+ - ./lib/xencap/xenapi.rb
27
+ - ./lib/xencap.rb
28
+ homepage: https://github.com/plainlystated/xencap
29
+ licenses: []
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 1.8.24
49
+ signing_key:
50
+ specification_version: 3
51
+ summary: Capistrano support for managing XenServer
52
+ test_files: []