kytoon 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +29 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +81 -0
- data/Rakefile +35 -0
- data/VERSION +1 -0
- data/config/server_group_vpc.json +14 -0
- data/config/server_group_xen.json +24 -0
- data/lib/kytoon.rb +8 -0
- data/lib/kytoon/providers/cloud_servers_vpc.rb +6 -0
- data/lib/kytoon/providers/cloud_servers_vpc/client.rb +197 -0
- data/lib/kytoon/providers/cloud_servers_vpc/connection.rb +148 -0
- data/lib/kytoon/providers/cloud_servers_vpc/server.rb +121 -0
- data/lib/kytoon/providers/cloud_servers_vpc/server_group.rb +401 -0
- data/lib/kytoon/providers/cloud_servers_vpc/ssh_public_key.rb +29 -0
- data/lib/kytoon/providers/cloud_servers_vpc/vpn_network_interface.rb +33 -0
- data/lib/kytoon/providers/xenserver.rb +1 -0
- data/lib/kytoon/providers/xenserver/server_group.rb +371 -0
- data/lib/kytoon/server_group.rb +46 -0
- data/lib/kytoon/ssh_util.rb +23 -0
- data/lib/kytoon/util.rb +118 -0
- data/lib/kytoon/version.rb +8 -0
- data/lib/kytoon/vpn/vpn_connection.rb +46 -0
- data/lib/kytoon/vpn/vpn_network_manager.rb +237 -0
- data/lib/kytoon/vpn/vpn_openvpn.rb +112 -0
- data/lib/kytoon/xml_util.rb +15 -0
- data/rake/kytoon.rake +115 -0
- data/test/client_test.rb +111 -0
- data/test/helper.rb +18 -0
- data/test/server_group_test.rb +253 -0
- data/test/server_test.rb +69 -0
- data/test/ssh_util_test.rb +22 -0
- data/test/test_helper.rb +194 -0
- data/test/test_kytoon.rb +7 -0
- data/test/util_test.rb +23 -0
- data/test/vpn_network_manager_test.rb +40 -0
- metadata +247 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rdoc", "~> 3.12"
|
10
|
+
gem "bundler", "~> 1.1.4"
|
11
|
+
gem "builder", "~> 3.0.0"
|
12
|
+
gem "jeweler", "~> 1.8.3"
|
13
|
+
gem "uuidtools", "~> 2.1.2"
|
14
|
+
gem "mocha", "~> 0.12.1"
|
15
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
builder (3.0.0)
|
5
|
+
git (1.2.5)
|
6
|
+
jeweler (1.8.4)
|
7
|
+
bundler (~> 1.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
rdoc
|
11
|
+
json (1.7.3)
|
12
|
+
metaclass (0.0.1)
|
13
|
+
mocha (0.12.1)
|
14
|
+
metaclass (~> 0.0.1)
|
15
|
+
rake (0.9.2.2)
|
16
|
+
rdoc (3.12)
|
17
|
+
json (~> 1.4)
|
18
|
+
uuidtools (2.1.2)
|
19
|
+
|
20
|
+
PLATFORMS
|
21
|
+
ruby
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
builder (~> 3.0.0)
|
25
|
+
bundler (~> 1.1.4)
|
26
|
+
jeweler (~> 1.8.3)
|
27
|
+
mocha (~> 0.12.1)
|
28
|
+
rdoc (~> 3.12)
|
29
|
+
uuidtools (~> 2.1.2)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Red Hat Inc.
|
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.
|
data/README.rdoc
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
= Kytoon
|
2
|
+
|
3
|
+
Create & configure ephemeral virtual private clouds
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
A set of Rake tasks that provide a framework to help automate the creation and configuration of “identical” VPC server groups. Kytoon provides the ability to create projects that can be used by team members and continuous integration systems to spin up groups of servers for development and/or testing. Configuration information is stored in JSON and YAML formats which can be easily parsed, edited, and version controlled.
|
8
|
+
|
9
|
+
Inspired by and based on the Chef VPC Toolkit.
|
10
|
+
|
11
|
+
== Supports
|
12
|
+
|
13
|
+
- Cloud Servers VPC (Rackspace and OpenStack)
|
14
|
+
- XenServer
|
15
|
+
|
16
|
+
== Installation
|
17
|
+
|
18
|
+
1) Gem install kytoon.
|
19
|
+
|
20
|
+
2) Add Kytoon tasks to your projects Rakefile:
|
21
|
+
|
22
|
+
KYTOON_PROJECT = "#{File.dirname(__FILE__)}" unless defined?(KYTOON_PROJECT)
|
23
|
+
require 'rubygems'
|
24
|
+
require 'kytoon'
|
25
|
+
include Kytoon
|
26
|
+
|
27
|
+
3) Create a .kytoon.conf file in your HOME directory that contains the following:
|
28
|
+
|
29
|
+
# Set one of the following group_types
|
30
|
+
group_type: cloud_server_vpc
|
31
|
+
#group_type: xenserver
|
32
|
+
|
33
|
+
# Cloud Servers VPC credentials
|
34
|
+
cloud_servers_vpc_url: https://your.vpc.url/
|
35
|
+
cloud_servers_vpc_username: <username>
|
36
|
+
cloud_servers_vpc_password: <password>
|
37
|
+
|
38
|
+
== Examples
|
39
|
+
|
40
|
+
Example commands:
|
41
|
+
|
42
|
+
- Create a new server group.
|
43
|
+
|
44
|
+
$ rake group:create
|
45
|
+
|
46
|
+
- List your currently running server groups.
|
47
|
+
|
48
|
+
$ rake group:list
|
49
|
+
|
50
|
+
- SSH into the current (most recently created) server group
|
51
|
+
|
52
|
+
$ rake ssh
|
53
|
+
|
54
|
+
- SSH into a server group with an ID of 3
|
55
|
+
|
56
|
+
$ rake ssh GROUP_ID=3
|
57
|
+
|
58
|
+
- Delete the server group with an ID of 3
|
59
|
+
|
60
|
+
$ rake group:delete GROUP_ID=3
|
61
|
+
|
62
|
+
|
63
|
+
== Bash Automation Script
|
64
|
+
|
65
|
+
The following is an example bash script to spin up a group and run commands via SSH.
|
66
|
+
|
67
|
+
#!/bin/bash
|
68
|
+
trap "rake group:delete" INT TERM EXIT # cleanup the group on exit
|
69
|
+
|
70
|
+
# create a server group
|
71
|
+
rake group:create
|
72
|
+
|
73
|
+
# Run some scripts on the login server
|
74
|
+
rake ssh bash <<-EOF_BASH
|
75
|
+
echo 'It works!'
|
76
|
+
EOF_BASH
|
77
|
+
|
78
|
+
|
79
|
+
== Copyright
|
80
|
+
|
81
|
+
Copyright (c) 2012 Red Hat Inc. See LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
KYTOON_PROJECT = "#{File.dirname(__FILE__)}" unless defined?(KYTOON_PROJECT)
|
4
|
+
|
5
|
+
$:.unshift File.join(File.dirname(__FILE__),'lib')
|
6
|
+
require 'kytoon'
|
7
|
+
include Kytoon
|
8
|
+
|
9
|
+
Dir[File.join(File.dirname(__FILE__), 'rake', '*.rake')].each do |rakefile|
|
10
|
+
import(rakefile)
|
11
|
+
end
|
12
|
+
|
13
|
+
Rake::TestTask.new(:test) do |t|
|
14
|
+
t.pattern = 'test/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
Rake::Task['test'].comment = "Unit"
|
18
|
+
|
19
|
+
begin
|
20
|
+
require 'jeweler'
|
21
|
+
Jeweler::Tasks.new do |gemspec|
|
22
|
+
gemspec.name = "kytoon"
|
23
|
+
gemspec.summary = "Create & configure ephemeral virtual private clouds."
|
24
|
+
gemspec.description = "A set of Rake tasks that provide a framework to help automate the creation and configuration of VPC server groups."
|
25
|
+
gemspec.email = "dprince@redhat.com"
|
26
|
+
gemspec.homepage = "http://github.com/dprince/kytoon"
|
27
|
+
gemspec.authors = ["Dan Prince"]
|
28
|
+
gemspec.add_dependency 'rake'
|
29
|
+
gemspec.add_dependency 'builder'
|
30
|
+
gemspec.add_dependency 'json'
|
31
|
+
gemspec.add_dependency 'uuidtools'
|
32
|
+
end
|
33
|
+
rescue LoadError
|
34
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
35
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,24 @@
|
|
1
|
+
{
|
2
|
+
"name": "Fedora",
|
3
|
+
"netmask": "255.255.255.0",
|
4
|
+
"gateway": "192.168.0.1",
|
5
|
+
"broadcast": "192.168.0.127",
|
6
|
+
"dns_nameserver": "8.8.8.8",
|
7
|
+
"network_type": "static",
|
8
|
+
"public_ip_bridge": "xenbr0",
|
9
|
+
"bridge": "xenbr1",
|
10
|
+
"servers": [
|
11
|
+
{
|
12
|
+
"hostname": "login",
|
13
|
+
"image_path": "/images/fedora-agent2.xva",
|
14
|
+
"ip_address": "192.168.0.2",
|
15
|
+
"mac": "e2:6d:71:67:7e:66"
|
16
|
+
},
|
17
|
+
{
|
18
|
+
"hostname": "nova1",
|
19
|
+
"image_path": "/images/fedora-agent2.xva",
|
20
|
+
"ip_address": "192.168.0.3",
|
21
|
+
"mac": "e2:ad:a1:a7:ae:67"
|
22
|
+
}
|
23
|
+
]
|
24
|
+
}
|
data/lib/kytoon.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
require 'kytoon/providers/cloud_servers_vpc/connection'
|
2
|
+
require 'kytoon/providers/cloud_servers_vpc/client'
|
3
|
+
require 'kytoon/providers/cloud_servers_vpc/server'
|
4
|
+
require 'kytoon/providers/cloud_servers_vpc/server_group'
|
5
|
+
require 'kytoon/providers/cloud_servers_vpc/ssh_public_key'
|
6
|
+
require 'kytoon/providers/cloud_servers_vpc/vpn_network_interface'
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module Kytoon
|
2
|
+
|
3
|
+
module Providers
|
4
|
+
|
5
|
+
module CloudServersVPC
|
6
|
+
|
7
|
+
class Client
|
8
|
+
|
9
|
+
@@data_dir=File.join(KYTOON_PROJECT, "tmp", "clients")
|
10
|
+
|
11
|
+
def self.data_dir
|
12
|
+
@@data_dir
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.data_dir=(dir)
|
16
|
+
@@data_dir=dir
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :id
|
20
|
+
attr_accessor :name
|
21
|
+
attr_accessor :description
|
22
|
+
attr_accessor :status
|
23
|
+
attr_accessor :server_group_id
|
24
|
+
attr_accessor :cache_file
|
25
|
+
|
26
|
+
def initialize(options={})
|
27
|
+
@id=options[:id].to_i
|
28
|
+
@name=options[:name]
|
29
|
+
@description=options[:description]
|
30
|
+
if options[:status]
|
31
|
+
@status=options[:status]
|
32
|
+
else
|
33
|
+
@status = "Pending"
|
34
|
+
end
|
35
|
+
@status=options[:status] or @status = "Pending"
|
36
|
+
@server_group_id=options[:server_group_id]
|
37
|
+
if options[:cache_file] then
|
38
|
+
@cache_file=options[:cache_file]
|
39
|
+
else
|
40
|
+
@cache_file=options[:server_group_id]
|
41
|
+
end
|
42
|
+
@vpn_network_interfaces=[]
|
43
|
+
end
|
44
|
+
|
45
|
+
def vpn_network_interfaces
|
46
|
+
@vpn_network_interfaces
|
47
|
+
end
|
48
|
+
|
49
|
+
def cache_to_disk
|
50
|
+
FileUtils.mkdir_p(@@data_dir)
|
51
|
+
File.open(File.join(@@data_dir, "#{@cache_file}.xml"), 'w') do |f|
|
52
|
+
f.chmod(0600)
|
53
|
+
f.write(self.to_xml)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete
|
58
|
+
client_xml_file=File.join(@@data_dir, "#{@cache_file}.xml")
|
59
|
+
if File.exists?(client_xml_file) then
|
60
|
+
File.delete(client_xml_file)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.from_xml(xml)
|
65
|
+
client=nil
|
66
|
+
dom = REXML::Document.new(xml)
|
67
|
+
REXML::XPath.each(dom, "/client") do |cxml|
|
68
|
+
|
69
|
+
client=Client.new(
|
70
|
+
:id => XMLUtil.element_text(cxml,"id").to_i,
|
71
|
+
:name => XMLUtil.element_text(cxml, "name"),
|
72
|
+
:description => XMLUtil.element_text(cxml,"description"),
|
73
|
+
:status => XMLUtil.element_text(cxml,"status"),
|
74
|
+
:server_group_id => XMLUtil.element_text(cxml, "server-group-id").to_i
|
75
|
+
)
|
76
|
+
REXML::XPath.each(dom, "//vpn-network-interface") do |vni|
|
77
|
+
vni = VpnNetworkInterface.new(
|
78
|
+
:id => XMLUtil.element_text(vni, "id"),
|
79
|
+
:vpn_ip_addr => XMLUtil.element_text(vni, "vpn-ip-addr"),
|
80
|
+
:ptp_ip_addr => XMLUtil.element_text(vni, "ptp-ip-addr"),
|
81
|
+
:client_key => XMLUtil.element_text(vni, "client-key"),
|
82
|
+
:client_cert => XMLUtil.element_text(vni, "client-cert"),
|
83
|
+
:ca_cert => XMLUtil.element_text(vni, "ca-cert")
|
84
|
+
)
|
85
|
+
client.vpn_network_interfaces << vni
|
86
|
+
end
|
87
|
+
end
|
88
|
+
client
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_xml
|
92
|
+
|
93
|
+
xml = Builder::XmlMarkup.new
|
94
|
+
xml.tag! "client" do |sg|
|
95
|
+
sg.id(@id)
|
96
|
+
sg.name(@name)
|
97
|
+
sg.description(@description)
|
98
|
+
sg.status(@status)
|
99
|
+
sg.tag! "server-group-id", @server_group_id
|
100
|
+
sg.tag! "vpn-network-interfaces", {"type" => "array"} do |interfaces|
|
101
|
+
@vpn_network_interfaces.each do |vni|
|
102
|
+
interfaces.tag! "vpn-network-interface" do |xml_vni|
|
103
|
+
xml_vni.id(vni.id)
|
104
|
+
xml_vni.tag! "vpn-ip-addr", vni.vpn_ip_addr
|
105
|
+
xml_vni.tag! "ptp-ip-addr", vni.ptp_ip_addr
|
106
|
+
xml_vni.tag! "client-key", vni.client_key
|
107
|
+
xml_vni.tag! "client-cert", vni.client_cert
|
108
|
+
xml_vni.tag! "ca-cert", vni.ca_cert
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
xml.target!
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
# Poll the server group until it is online.
|
120
|
+
# :timeout - max number of seconds to wait before raising an exception.
|
121
|
+
# Defaults to 1500
|
122
|
+
def poll_until_online(options={})
|
123
|
+
|
124
|
+
timeout=options[:timeout] or timeout = ENV['VPN_CLIENT_TIMEOUT']
|
125
|
+
if timeout.nil? or timeout.empty? then
|
126
|
+
timeout=300 # defaults to 5 minutes
|
127
|
+
end
|
128
|
+
|
129
|
+
online = false
|
130
|
+
count=0
|
131
|
+
until online or (count*5) >= timeout.to_i do
|
132
|
+
count+=1
|
133
|
+
begin
|
134
|
+
client=Client.get(:id => @id, :source => "remote")
|
135
|
+
|
136
|
+
if client.status == "Online" then
|
137
|
+
online = true
|
138
|
+
else
|
139
|
+
yield client if block_given?
|
140
|
+
sleep 5
|
141
|
+
end
|
142
|
+
rescue EOFError
|
143
|
+
end
|
144
|
+
end
|
145
|
+
if (count*20) >= timeout.to_i then
|
146
|
+
raise "Timeout waiting for client to come online."
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.create(server_group, client_name, cache_to_disk=true)
|
152
|
+
|
153
|
+
xml = Builder::XmlMarkup.new
|
154
|
+
xml.client do |client|
|
155
|
+
client.name(client_name)
|
156
|
+
client.description("Toolkit Client: #{client_name}")
|
157
|
+
client.tag! "server-group-id", server_group.id
|
158
|
+
end
|
159
|
+
|
160
|
+
xml=Connection.post("/clients.xml", xml.target!)
|
161
|
+
client=Client.from_xml(xml)
|
162
|
+
client.cache_to_disk if cache_to_disk
|
163
|
+
client
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
# Get a client. The following options are available:
|
168
|
+
#
|
169
|
+
# :id - The ID of the client to get.
|
170
|
+
# :source - valid options are 'remote' and 'cache'
|
171
|
+
#
|
172
|
+
def self.get(options = {})
|
173
|
+
|
174
|
+
source = options[:source] or source = "remote"
|
175
|
+
|
176
|
+
if source == "remote" then
|
177
|
+
id=options[:id] or raise "Please specify a Client ID."
|
178
|
+
xml=Connection.get("/clients/#{id}.xml")
|
179
|
+
Client.from_xml(xml)
|
180
|
+
elsif source == "cache" then
|
181
|
+
id=options[:id] or id = ENV['GROUP_ID']
|
182
|
+
client_xml_file=File.join(@@data_dir, "#{id}.xml")
|
183
|
+
raise "No client files exist." if not File.exists?(client_xml_file)
|
184
|
+
Client.from_xml(IO.read(client_xml_file))
|
185
|
+
else
|
186
|
+
raise "Invalid get :source specified."
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|