kytoon 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.
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