kytoon 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.document +5 -0
  2. data/Gemfile +15 -0
  3. data/Gemfile.lock +29 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +81 -0
  6. data/Rakefile +35 -0
  7. data/VERSION +1 -0
  8. data/config/server_group_vpc.json +14 -0
  9. data/config/server_group_xen.json +24 -0
  10. data/lib/kytoon.rb +8 -0
  11. data/lib/kytoon/providers/cloud_servers_vpc.rb +6 -0
  12. data/lib/kytoon/providers/cloud_servers_vpc/client.rb +197 -0
  13. data/lib/kytoon/providers/cloud_servers_vpc/connection.rb +148 -0
  14. data/lib/kytoon/providers/cloud_servers_vpc/server.rb +121 -0
  15. data/lib/kytoon/providers/cloud_servers_vpc/server_group.rb +401 -0
  16. data/lib/kytoon/providers/cloud_servers_vpc/ssh_public_key.rb +29 -0
  17. data/lib/kytoon/providers/cloud_servers_vpc/vpn_network_interface.rb +33 -0
  18. data/lib/kytoon/providers/xenserver.rb +1 -0
  19. data/lib/kytoon/providers/xenserver/server_group.rb +371 -0
  20. data/lib/kytoon/server_group.rb +46 -0
  21. data/lib/kytoon/ssh_util.rb +23 -0
  22. data/lib/kytoon/util.rb +118 -0
  23. data/lib/kytoon/version.rb +8 -0
  24. data/lib/kytoon/vpn/vpn_connection.rb +46 -0
  25. data/lib/kytoon/vpn/vpn_network_manager.rb +237 -0
  26. data/lib/kytoon/vpn/vpn_openvpn.rb +112 -0
  27. data/lib/kytoon/xml_util.rb +15 -0
  28. data/rake/kytoon.rake +115 -0
  29. data/test/client_test.rb +111 -0
  30. data/test/helper.rb +18 -0
  31. data/test/server_group_test.rb +253 -0
  32. data/test/server_test.rb +69 -0
  33. data/test/ssh_util_test.rb +22 -0
  34. data/test/test_helper.rb +194 -0
  35. data/test/test_kytoon.rb +7 -0
  36. data/test/util_test.rb +23 -0
  37. data/test/vpn_network_manager_test.rb +40 -0
  38. metadata +247 -0
@@ -0,0 +1,29 @@
1
+ module Kytoon
2
+
3
+ module Providers
4
+
5
+ module CloudServersVPC
6
+
7
+ class SshPublicKey
8
+
9
+ attr_accessor :id
10
+ attr_accessor :description
11
+ attr_accessor :public_key
12
+ attr_accessor :server_group_id
13
+
14
+ def initialize(options={})
15
+
16
+ @id=options[:id]
17
+ @description=options[:description]
18
+ @public_key=options[:public_key]
19
+ @server_group_id=options[:server_group_id]
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,33 @@
1
+ module Kytoon
2
+
3
+ module Providers
4
+
5
+ module CloudServersVPC
6
+
7
+ class VpnNetworkInterface
8
+
9
+ attr_accessor :id
10
+ attr_accessor :vpn_ip_addr
11
+ attr_accessor :ptp_ip_addr
12
+ attr_accessor :client_key
13
+ attr_accessor :client_cert
14
+ attr_accessor :ca_cert
15
+
16
+ def initialize(options={})
17
+
18
+ @id=options[:id].to_i
19
+ @vpn_ip_addr=options[:vpn_ip_addr]
20
+ @ptp_ip_addr=options[:ptp_ip_addr]
21
+ @client_key=options[:client_key]
22
+ @client_cert=options[:client_cert]
23
+ @ca_cert=options[:ca_cert]
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1 @@
1
+ require 'kytoon/providers/xenserver/server_group'
@@ -0,0 +1,371 @@
1
+ require 'json'
2
+ require 'builder'
3
+ require 'fileutils'
4
+ require 'rexml/document'
5
+ require 'kytoon/util'
6
+ require 'base64'
7
+
8
+ module Kytoon
9
+
10
+ module Providers
11
+
12
+ module Xenserver
13
+ # All in one XenServer server group provider.
14
+ #
15
+ # Required setup:
16
+ # 1) add your ssh key to the XenServer box.
17
+ #
18
+ # 2) Pre-download any .xva images you'd like to use. These images *must*
19
+ # have the OpenStack guest agent installed (including Xen Guest Tools).
20
+ #
21
+ # 3) Generate an ssh keypair on your XenServer host.
22
+ #
23
+ # 4) Add an ip to the private Xen bridge you'd like to use for instances.
24
+ # This IP should match the range you'd like to use in your server group
25
+ # config file. Example:
26
+ # ip addr add 192.168.0.1/24 brd 192.168.0.255 scope global dev xenbr1
27
+ #
28
+ class ServerGroup
29
+
30
+ @@data_dir=File.join(KYTOON_PROJECT, "tmp", "xenserver")
31
+
32
+ def self.data_dir
33
+ @@data_dir
34
+ end
35
+
36
+ def self.data_dir=(dir)
37
+ @@data_dir=dir
38
+ end
39
+
40
+ CONFIG_FILE = KYTOON_PROJECT + File::SEPARATOR + "config" + File::SEPARATOR + "server_group.json"
41
+
42
+ attr_accessor :id
43
+ attr_accessor :name
44
+ attr_accessor :gateway_ip
45
+ attr_accessor :netmask
46
+ attr_accessor :gateway
47
+ attr_accessor :broadcast
48
+ attr_accessor :network_type
49
+ attr_accessor :bridge
50
+ attr_accessor :public_ip_bridge
51
+ attr_accessor :dns_nameserver
52
+
53
+ def initialize(options={})
54
+ @id = options[:id] || Time.now.to_i
55
+ @name = options[:name]
56
+ @netmask = options[:netmask]
57
+ @gateway = options[:gateway]
58
+ @broadcast = options[:broadcast]
59
+ @network_type = options[:network_type]
60
+ @bridge = options[:bridge]
61
+ @public_ip_bridge = options[:public_ip_bridge]
62
+ @dns_nameserver = options[:dns_nameserver]
63
+ @gateway_ip = options[:gateway_ip]
64
+ @gateway_ip = ENV['GATEWAY_IP'] if @gateway_ip.nil?
65
+ raise "Please specify a GATEWAY_IP" if @gateway_ip.nil?
66
+
67
+ @servers=[]
68
+ end
69
+
70
+ def server(name)
71
+ @servers.select {|s| s['hostname'] == name}[0] if @servers.size > 0
72
+ end
73
+
74
+ def servers
75
+ @servers
76
+ end
77
+
78
+ def gateway_ip
79
+ @gateway_ip
80
+ end
81
+
82
+ # generate a Server Group XML from server_group.json
83
+ def self.from_json(json)
84
+
85
+ json_hash=JSON.parse(json)
86
+
87
+ sg=ServerGroup.new(
88
+ :id => json_hash["id"],
89
+ :name => json_hash["name"],
90
+ :netmask => json_hash['netmask'],
91
+ :gateway => json_hash['gateway'],
92
+ :gateway_ip => json_hash['gateway_ip'],
93
+ :broadcast => json_hash['broadcast'],
94
+ :dns_nameserver => json_hash['dns_nameserver'],
95
+ :network_type => json_hash['network_type'],
96
+ :public_ip_bridge => json_hash['public_ip_bridge'],
97
+ :bridge => json_hash['bridge']
98
+ )
99
+ json_hash["servers"].each do |server_hash|
100
+ sg.servers << {
101
+ 'hostname' => server_hash['hostname'],
102
+ 'ip_address' => server_hash['ip_address'],
103
+ 'mac' => server_hash['mac'],
104
+ 'image_path' => server_hash['image_path']
105
+ }
106
+ end
107
+ return sg
108
+
109
+ end
110
+
111
+ def pretty_print
112
+
113
+ puts "Group ID: #{@id}"
114
+ puts "name: #{@name}"
115
+ puts "Servers:"
116
+ servers.each do |server|
117
+ puts "\tname: #{server['hostname']}"
118
+ puts "\t--"
119
+ end
120
+
121
+ end
122
+
123
+ def server_names
124
+
125
+ names=[]
126
+
127
+ servers.each do |server|
128
+ if block_given? then
129
+ yield server['hostname']
130
+ else
131
+ names << server['hostname']
132
+ end
133
+ end
134
+
135
+ names
136
+
137
+ end
138
+
139
+ def cache_to_disk
140
+
141
+ sg_hash = {
142
+ 'id' => @id,
143
+ 'name' => @name,
144
+ 'netmask' => @netmask,
145
+ 'gateway' => @gateway,
146
+ 'gateway_ip' => @gateway_ip,
147
+ 'broadcast' => @broadcast,
148
+ 'dns_nameserver' => @dns_nameserver,
149
+ 'network_type' => @network_type,
150
+ 'public_ip_bridge' => @public_ip_bridge,
151
+ 'bridge' => @bridge,
152
+ 'servers' => []
153
+ }
154
+ @servers.each do |server|
155
+ sg_hash['servers'] << {'hostname' => server['hostname'], 'ip_address' => server['ip_address'], 'image_path' => server['image_path'], 'mac' => server['mac']}
156
+ end
157
+
158
+ FileUtils.mkdir_p(@@data_dir)
159
+ File.open(File.join(@@data_dir, "#{@id}.json"), 'w') do |f|
160
+ f.chmod(0600)
161
+ f.write(sg_hash.to_json)
162
+ end
163
+ end
164
+
165
+ def delete
166
+ ServerGroup.cleanup_instances(@gateway_ip)
167
+ out_file=File.join(@@data_dir, "#{@id}.json")
168
+ File.delete(out_file) if File.exists?(out_file)
169
+ end
170
+
171
+ def self.create(sg)
172
+ sg.cache_to_disk
173
+ init_host(sg)
174
+ status, host_ssh_public_key = Kytoon::Util.remote_exec(%{
175
+ if [ -f /root/.ssh/id_rsa.pub ]; then
176
+ cat /root/.ssh/id_rsa.pub
177
+ elif [ -f /root/.ssh/id_dsa.pub ]; then
178
+ cat /root/.ssh/id_dsa.pub
179
+ else
180
+ exit 1
181
+ fi
182
+ }, sg.gateway_ip)
183
+ sg.servers.each do |server|
184
+ create_instance(sg.gateway_ip, server['image_path'], server['hostname'], server['mac'], sg.bridge, host_ssh_public_key)
185
+ network_type = sg.network_type
186
+ if network_type == 'static' then
187
+ configure_static_networking(sg.gateway_ip, server['hostname'], server['ip_address'], sg.netmask, sg.gateway, sg.broadcast, server['mac'], sg.dns_nameserver)
188
+ else
189
+ raise "Unsupported network type '#{sg.network_type}'"
190
+ end
191
+ end
192
+ sg
193
+ end
194
+
195
+ def self.get(options={})
196
+ id = options[:id]
197
+ if id.nil? then
198
+ group=ServerGroup.most_recent
199
+ raise "No server group files exist." if group.nil?
200
+ id=group.id
201
+ end
202
+
203
+ out_file=File.join(@@data_dir, "#{id}.json")
204
+ raise "No server group files exist." if not File.exists?(out_file)
205
+ ServerGroup.from_json(IO.read(out_file))
206
+ end
207
+
208
+ def self.index(options={})
209
+
210
+ server_groups=[]
211
+ Dir[File.join(ServerGroup.data_dir, '*.json')].each do |file|
212
+ server_groups << ServerGroup.from_json(IO.read(file))
213
+ end
214
+ server_groups
215
+
216
+ end
217
+
218
+ def self.most_recent
219
+ server_groups=[]
220
+ Dir[File.join(@@data_dir, "*.json")].each do |file|
221
+ server_groups << ServerGroup.from_json(IO.read(file))
222
+ end
223
+ if server_groups.size > 0 then
224
+ server_groups.sort { |a,b| b.id <=> a.id }[0]
225
+ else
226
+ nil
227
+ end
228
+ end
229
+
230
+ def self.init_host(sg)
231
+
232
+ hosts_file_data = "127.0.0.1\tlocalhost localhost.localdomain\n"
233
+ sg.servers.each do |server|
234
+ hosts_file_data += "#{server['ip_address']}\t#{server['hostname']}\n"
235
+ end
236
+
237
+ Kytoon::Util.remote_exec(%{
238
+ cat > /etc/hosts <<-EOF_CAT
239
+ #{hosts_file_data}
240
+ EOF_CAT
241
+ # FIXME... probably a bit insecure but most people are probably using
242
+ # boxes behind another firewall anyway.
243
+ iptables -F
244
+ iptables -P INPUT ACCEPT
245
+ iptables -P FORWARD ACCEPT
246
+ iptables -P OUTPUT ACCEPT
247
+ iptables -t nat -A POSTROUTING -o #{sg.public_ip_bridge} -j MASQUERADE
248
+ echo 1 > /proc/sys/net/ipv4/ip_forward
249
+ }, sg.gateway_ip)
250
+ end
251
+
252
+ def self.create_instance(gw_ip, image_path, hostname, mac, xen_bridge='xenbr1', ssh_public_key=nil)
253
+ file_data = Base64.encode64("/root/.ssh/authorized_keys,#{ssh_public_key}")
254
+
255
+ Kytoon::Util.remote_exec(%{
256
+ UUID=$(xe vm-import filename=#{image_path})
257
+ xe vm-param-set name-label=#{hostname} uuid=$UUID
258
+ NETWORK_UUID=$(xe network-list bridge=#{xen_bridge} | grep -P "^uuid" | cut -f2 -d: | cut -f2 -d" ")
259
+ xe vif-destroy uuid=$VIF_UUID &> /dev/null
260
+ for VIF_UUID in $(xe vif-list vm-uuid=$UUID | grep uuid | sed -e 's|.*: ||'); do
261
+ echo "Destroying Xen VIF uuid: $VIF_UUID"
262
+ xe vif-destroy uuid=$VIF_UUID &> /dev/null
263
+ done
264
+ xe vif-create vm-uuid=$UUID mac=#{mac} network-uuid=$NETWORK_UUID device=0 &> /dev/null
265
+ xe vm-start uuid=$UUID &> /dev/null
266
+
267
+ # inject ssh from host
268
+ DOMID=$(xe vm-param-get uuid=$UUID param-name="dom-id")
269
+ xenstore-rm -s /local/domain/$DOMID/data/guest/ssh_key 2> /dev/null
270
+ xenstore-write -s /local/domain/$DOMID/data/host/ssh_key '{"name": "injectfile", "value": "#{file_data}"}'
271
+ until [ -n "$INJECT_RETVAL" ]; do
272
+ INJECT_RETVAL=$(xenstore-read -s /local/domain/$DOMID/data/guest/ssh_key 2> /dev/null)
273
+ done
274
+ xenstore-rm -s /local/domain/$DOMID/data/host/ssh_key
275
+
276
+ }, gw_ip) do |ok, out|
277
+ if not ok
278
+ puts out
279
+ raise "Failed to create instance #{hostname}."
280
+ end
281
+ end
282
+ end
283
+
284
+ def self.cleanup_instances(gw_ip)
285
+ Kytoon::Util.remote_exec(%{
286
+ rpm -ev openstack-xen-plugins &> /dev/null
287
+ yum clean all &> /dev/null
288
+
289
+ for UUID in $(xe vm-list is-control-domain=false | grep uuid | sed -e 's|.*: ||'); do
290
+ echo "Destroying Xen instance uuid: $UUID"
291
+ xe vm-shutdown uuid=$UUID
292
+ xe vm-destroy uuid=$UUID
293
+ done
294
+
295
+ for UUID in $(xe vdi-list read-only=false | grep "^uuid" | sed -e 's|.*: ||'); do
296
+ echo "Destroying VDI uuid: $UUID"
297
+ xe vdi-destroy uuid=$UUID
298
+ done
299
+
300
+ for UUID in $(xe vif-list | grep uuid | sed -e 's|.*: ||'); do
301
+ echo "Destroying Xen VIF uuid: $UUID"
302
+ xe vif-destroy uuid=$UUID
303
+ done
304
+
305
+ }, gw_ip) do |ok, out|
306
+ if not ok
307
+ puts out
308
+ raise "Failed to cleanup instances."
309
+ end
310
+ end
311
+ end
312
+
313
+ def self.configure_static_networking(gw_ip, hostname, ip_address, netmask, gateway, broadcast, mac, dns_nameserver)
314
+
315
+ # networking
316
+ network_info = {
317
+ "label" => "public",
318
+ "broadcast" => broadcast,
319
+ "ips" => [{
320
+ "ip" => ip_address,
321
+ "netmask" => netmask,
322
+ "enabled" => "1"}],
323
+ "mac" => mac,
324
+ "dns" => [dns_nameserver],
325
+ "gateway" => gateway
326
+ }
327
+
328
+ Kytoon::Util.remote_exec(%{
329
+ UUID=$(xe vm-list name-label=#{hostname} | grep uuid | sed -e 's|.*: ||')
330
+ DOMID=$(xe vm-param-get uuid=$UUID param-name="dom-id")
331
+ xenstore-write -s /local/domain/$DOMID/vm-data/hostname '#{hostname}'
332
+ xenstore-write -s /local/domain/$DOMID/vm-data/networking/123_nw_info '#{network_info.to_json}'
333
+
334
+ xenstore-write -s /local/domain/$DOMID/data/host/123_reset_nw '{"name": "resetnetwork", "value": ""}'
335
+ xenstore-rm -s /local/domain/$DOMID/data/guest/123_reset_nw 2> /dev/null
336
+ until [ -n "$NW_RETVAL" ]; do
337
+ NW_RETVAL=$(xenstore-read -s /local/domain/$DOMID/data/guest/123_reset_nw 2> /dev/null)
338
+ done
339
+ xenstore-rm -s /local/domain/$DOMID/data/host/123_reset_nw
340
+ xe vm-reboot uuid=$UUID &> /dev/null
341
+ COUNT=0
342
+ until ping -c 1 #{hostname} &> /dev/null; do
343
+ COUNT=$(( $COUNT + 1 ))
344
+ [ $COUNT -eq 10 ] && break
345
+ done
346
+ until ssh -o ConnectTimeout=1 #{hostname} &> /dev/null; do
347
+ COUNT=$(( $COUNT + 1 ))
348
+ [ $COUNT -eq 20 ] && break
349
+ sleep 1
350
+ done
351
+ ssh #{hostname} bash <<-EOF_SSH_BASH
352
+ hostname #{hostname}
353
+ EOF_SSH_BASH
354
+ scp /etc/hosts #{hostname}:/etc/hosts
355
+ }, gw_ip) do |ok, out|
356
+ puts out
357
+ if not ok
358
+ puts out
359
+ raise "Failed to setup static networking for #{hostname}."
360
+ end
361
+ end
362
+
363
+ end
364
+
365
+ end
366
+
367
+ end
368
+
369
+ end
370
+
371
+ end
@@ -0,0 +1,46 @@
1
+ require 'kytoon/providers/cloud_servers_vpc'
2
+ require 'kytoon/providers/xenserver'
3
+
4
+ class ServerGroup
5
+
6
+ @@group_class = nil
7
+
8
+ # called to init the configured group class we will use
9
+ def self.init
10
+ return if not @@group_class.nil?
11
+ configs = Util.load_configs
12
+ group_type = ENV['GROUP_TYPE'] || configs['group_type']
13
+ if group_type == "cloud_server_vpc" then
14
+ @@group_class = Kytoon::Providers::CloudServersVPC::ServerGroup
15
+ elsif group_type == "xenserver" then
16
+ @@group_class = Kytoon::Providers::Xenserver::ServerGroup
17
+ else
18
+ raise "Invalid 'group_type' specified in config file."
19
+ end
20
+ end
21
+
22
+ def self.index(options)
23
+ @@group_class.index(options)
24
+ end
25
+
26
+ def self.create
27
+ json_config_file=ENV['SERVER_GROUP_JSON']
28
+ if json_config_file.nil? then
29
+ json_config_file = @@group_class::CONFIG_FILE
30
+ end
31
+ sg = @@group_class.from_json(IO.read(json_config_file))
32
+ @@group_class.create(sg)
33
+ end
34
+
35
+ def self.get
36
+ id = ENV['GROUP_ID']
37
+ @@group_class.get(:id => id)
38
+ end
39
+
40
+ def self.delete
41
+ id = ENV['GROUP_ID']
42
+ sg = @@group_class.get(:id => id)
43
+ sg.delete
44
+ end
45
+
46
+ end