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.
- data/lib/xencap/plugin.rb +28 -0
- data/lib/xencap/request_dispatcher.rb +21 -0
- data/lib/xencap/session_proxy/vm.rb +33 -0
- data/lib/xencap/session_proxy.rb +39 -0
- data/lib/xencap/tasks.rb +36 -0
- data/lib/xencap/xenapi.rb +232 -0
- data/lib/xencap.rb +9 -0
- metadata +52 -0
@@ -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
|
data/lib/xencap/tasks.rb
ADDED
@@ -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: []
|