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