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,148 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'openssl'
5
+
6
+ module Kytoon
7
+
8
+ module Providers
9
+
10
+ module CloudServersVPC
11
+
12
+ class Connection
13
+
14
+ MULTI_PART_BOUNDARY="jtZ!pZ1973um"
15
+
16
+ @@http=nil
17
+ @@auth_user=nil
18
+ @@auth_password=nil
19
+
20
+ def self.init_connection
21
+
22
+ configs=Util.load_configs
23
+
24
+ base_url = configs["cloud_servers_vpc_url"]
25
+ @@auth_user = configs["cloud_servers_vpc_username"]
26
+ @@auth_password = configs["cloud_servers_vpc_password"]
27
+
28
+ ssl_key = configs["ssl_key"]
29
+ ssl_cert = configs["ssl_cert"]
30
+ ssl_ca_cert = configs["ssl_ca_cert"]
31
+
32
+ url=URI.parse(base_url)
33
+ @@http = Net::HTTP.new(url.host,url.port)
34
+
35
+ if base_url =~ /^https/
36
+ @@http.use_ssl = true
37
+ if ssl_ca_cert then
38
+ @@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
39
+ else
40
+ @@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
41
+ end
42
+
43
+ if ssl_key then
44
+ pkey_data=IO.read(ssl_key)
45
+ if pkey_data =~ /^-----BEGIN RSA PRIVATE KEY-----/
46
+ @@http.key=OpenSSL::PKey::RSA.new(pkey_data)
47
+ else
48
+ @@http.key=OpenSSL::PKey::DSA.new(pkey_data)
49
+ end
50
+ end
51
+ @@http.cert=OpenSSL::X509::Certificate.new(IO.read(ssl_cert)) if ssl_cert
52
+ @@http.ca_file=ssl_ca_cert if ssl_ca_cert
53
+ end
54
+
55
+ end
56
+
57
+ def self.file_upload(url_path, file_data={}, post_data={})
58
+ init_connection if @@http.nil?
59
+ req = Net::HTTP::Post.new(url_path)
60
+
61
+ post_arr=[]
62
+ post_data.each_pair do |key, value|
63
+ post_arr << "--#{MULTI_PART_BOUNDARY}\r\n"
64
+ post_arr << "Content-Disposition: form-data; name=\"#{key}\"\r\n"
65
+ post_arr << "\r\n"
66
+ post_arr << value
67
+ post_arr << "\r\n"
68
+ end
69
+
70
+ file_data.each_pair do |name, file|
71
+ post_arr << "--#{MULTI_PART_BOUNDARY}\r\n"
72
+ post_arr << "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{File.basename(file)}\"\r\n"
73
+ post_arr << "Content-Type: text/plain\r\n"
74
+ post_arr << "\r\n"
75
+ post_arr << File.read(file)
76
+ post_arr << "\r\n--#{MULTI_PART_BOUNDARY}--\r\n"
77
+ end
78
+ post_arr << "--#{MULTI_PART_BOUNDARY}--\r\n\r\n"
79
+
80
+ req.body=post_arr.join
81
+
82
+ req.basic_auth @@auth_user, @@auth_password if @@auth_user and @@auth_password
83
+ req["Content-Type"] = "multipart/form-data, boundary=#{MULTI_PART_BOUNDARY}"
84
+
85
+ response = @@http.request(req)
86
+ case response
87
+ when Net::HTTPSuccess
88
+ return response.body
89
+ else
90
+ puts response.body
91
+ response.error!
92
+ end
93
+ end
94
+
95
+ def self.post(url_path, post_data)
96
+ init_connection if @@http.nil?
97
+ req = Net::HTTP::Post.new(url_path)
98
+ if post_data.kind_of?(String) then
99
+ req.body=post_data
100
+ elsif post_data.kind_of?(Hash) then
101
+ req.form_data=post_data
102
+ else
103
+ raise "Invalid post data type."
104
+ end
105
+ req.basic_auth @@auth_user, @@auth_password if @@auth_user and @@auth_password
106
+ response = @@http.request(req)
107
+ case response
108
+ when Net::HTTPSuccess
109
+ return response.body
110
+ else
111
+ puts response.body
112
+ response.error!
113
+ end
114
+ end
115
+
116
+ def self.get(url_path)
117
+ init_connection if @@http.nil?
118
+ req = Net::HTTP::Get.new(url_path)
119
+ req.basic_auth @@auth_user, @@auth_password if @@auth_user and @@auth_password
120
+ response = @@http.request(req)
121
+ case response
122
+ when Net::HTTPSuccess
123
+ return response.body
124
+ else
125
+ response.error!
126
+ end
127
+ end
128
+
129
+ def self.delete(url_path)
130
+ init_connection if @@http.nil?
131
+ req = Net::HTTP::Delete.new(url_path)
132
+ req.basic_auth @@auth_user, @@auth_password if @@auth_user and @@auth_password
133
+ response = @@http.request(req)
134
+ case response
135
+ when Net::HTTPSuccess
136
+ return response.body
137
+ else
138
+ response.error!
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ end
145
+
146
+ end
147
+
148
+ end
@@ -0,0 +1,121 @@
1
+ module Kytoon
2
+
3
+ module Providers
4
+
5
+ module CloudServersVPC
6
+
7
+ class Server
8
+
9
+ attr_accessor :id
10
+ attr_accessor :name
11
+ attr_accessor :description
12
+ attr_accessor :external_ip_addr
13
+ attr_accessor :internal_ip_addr
14
+ attr_accessor :cloud_server_id_number
15
+ attr_accessor :flavor_id
16
+ attr_accessor :image_id
17
+ attr_accessor :server_group_id
18
+ attr_accessor :openvpn_server
19
+ attr_accessor :retry_count
20
+ attr_accessor :error_message
21
+ attr_accessor :status
22
+ attr_accessor :admin_password
23
+
24
+ def initialize(options={})
25
+ @id=options[:id].to_i
26
+ @name=options[:name]
27
+ @description=options[:description] or @description=@name
28
+ @external_ip_addr=options[:external_ip_addr]
29
+ @internal_ip_addr=options[:internal_ip_addr]
30
+ @cloud_server_id_number=options[:cloud_server_id_number]
31
+ @flavor_id=options[:flavor_id]
32
+ @image_id=options[:image_id]
33
+ @admin_password=options[:admin_password]
34
+ @server_group_id=options[:server_group_id].to_i
35
+ @openvpn_server = [true, "true"].include?(options[:openvpn_server])
36
+ @retry_count=options[:retry_count].to_i or 0
37
+ @error_message=options[:error_message]
38
+ @status=options[:status]
39
+ end
40
+
41
+ def openvpn_server?
42
+ return @openvpn_server
43
+ end
44
+
45
+ def to_xml
46
+
47
+ xml = Builder::XmlMarkup.new
48
+ xml.tag! "server" do |server|
49
+ server.id(@id)
50
+ server.name(@name)
51
+ server.description(@description)
52
+ server.status(@status) if @status
53
+ server.tag! "external-ip-addr", @external_ip_addr if @external_ip_addr
54
+ server.tag! "internal-ip-addr", @internal_ip_addr if @internal_ip_addr
55
+ server.tag! "cloud-server-id-number", @cloud_server_id_number if @cloud_server_id_number
56
+ server.tag! "flavor-id", @flavor_id
57
+ server.tag! "image-id", @image_id
58
+ server.tag! "admin-password", @admin_password
59
+ server.tag! "server-group-id", @server_group_id
60
+ server.tag! "openvpn-server", "true" if openvpn_server?
61
+ server.tag! "error-message", @error_message if @error_message
62
+ end
63
+ xml.target!
64
+
65
+ end
66
+
67
+ def self.from_xml(xml)
68
+
69
+ server=nil
70
+ dom = REXML::Document.new(xml)
71
+ REXML::XPath.each(dom, "/*") do |sg_xml|
72
+
73
+ server=Server.new(
74
+ :id => XMLUtil.element_text(sg_xml, "id").to_i,
75
+ :name => XMLUtil.element_text(sg_xml, "name"),
76
+ :flavor_id => XMLUtil.element_text(sg_xml, "flavor-id"),
77
+ :image_id => XMLUtil.element_text(sg_xml, "image-id"),
78
+ :admin_password => XMLUtil.element_text(sg_xml, "admin-password"),
79
+ :description => XMLUtil.element_text(sg_xml, "description"),
80
+ :cloud_server_id_number => XMLUtil.element_text(sg_xml, "cloud-server-id-number"),
81
+ :description => XMLUtil.element_text(sg_xml, "description"),
82
+ :external_ip_addr => XMLUtil.element_text(sg_xml, "external-ip-addr"),
83
+ :internal_ip_addr => XMLUtil.element_text(sg_xml, "internal-ip-addr"),
84
+ :server_group_id => XMLUtil.element_text(sg_xml, "server-group-id"),
85
+ :openvpn_server => XMLUtil.element_text(sg_xml, "openvpn_server"),
86
+ :retry_count => XMLUtil.element_text(sg_xml, "retry-count"),
87
+ :error_message => XMLUtil.element_text(sg_xml, "error-message"),
88
+ :status => XMLUtil.element_text(sg_xml, "status")
89
+ )
90
+ end
91
+
92
+ server
93
+
94
+ end
95
+
96
+ def rebuild
97
+
98
+ raise "Error: Rebuilding the OpenVPN server is not supported at this time." if openvpn_server?
99
+
100
+ Connection.post("/servers/#{@id}/rebuild", {})
101
+
102
+ end
103
+
104
+ def delete
105
+ Connection.delete("/servers/#{@id}.xml")
106
+ end
107
+
108
+ def self.create(server)
109
+
110
+ xml=Connection.post("/servers.xml", server.to_xml)
111
+ server=Server.from_xml(xml)
112
+
113
+ end
114
+
115
+ end
116
+
117
+ end
118
+
119
+ end
120
+
121
+ end
@@ -0,0 +1,401 @@
1
+ require 'json'
2
+ require 'builder'
3
+ require 'fileutils'
4
+ require 'rexml/document'
5
+ require 'rexml/xpath'
6
+
7
+ module Kytoon
8
+
9
+ module Providers
10
+
11
+ module CloudServersVPC
12
+
13
+ class ServerGroup
14
+
15
+ @@data_dir=File.join(KYTOON_PROJECT, "tmp", "cloud_servers_vpc")
16
+
17
+ def self.data_dir
18
+ @@data_dir
19
+ end
20
+
21
+ def self.data_dir=(dir)
22
+ @@data_dir=dir
23
+ end
24
+
25
+ CONFIG_FILE = KYTOON_PROJECT + File::SEPARATOR + "config" + File::SEPARATOR + "server_group.json"
26
+
27
+ attr_accessor :id
28
+ attr_accessor :name
29
+ attr_accessor :description
30
+ attr_accessor :domain_name
31
+ attr_accessor :vpn_device
32
+ attr_accessor :vpn_proto
33
+ attr_accessor :vpn_network
34
+ attr_accessor :vpn_subnet
35
+ attr_accessor :owner_name
36
+
37
+ attr_reader :ssh_public_keys
38
+
39
+ def initialize(options={})
40
+ @id=options[:id]
41
+ @name=options[:name]
42
+ @description=options[:description]
43
+ @domain_name=options[:domain_name]
44
+ @vpn_device=options[:vpn_device] or @vpn_device="tun"
45
+ @vpn_proto=options[:vpn_proto] or @vpn_proto="tcp"
46
+ @vpn_network=options[:vpn_network] or @vpn_network="172.19.0.0"
47
+ @vpn_subnet=options[:vpn_subnet] or @vpn_subnet="255.255.128.0"
48
+ @owner_name=options[:owner_name] or @owner_name=ENV['USER']
49
+
50
+ @servers=[]
51
+ @clients=[]
52
+ @ssh_public_keys=[]
53
+ end
54
+
55
+ def server(name)
56
+ @servers.select {|s| s.name == name}[0] if @servers.size > 0
57
+ end
58
+
59
+ def servers
60
+ @servers
61
+ end
62
+
63
+ def client(name)
64
+ @clients.select {|s| s.name == name}[0] if @clients.size > 0
65
+ end
66
+
67
+ def clients
68
+ @clients
69
+ end
70
+
71
+ def vpn_gateway_name
72
+ @servers.select {|s| s.openvpn_server? }[0].name if @servers.size > 0
73
+ end
74
+
75
+ def gateway_ip
76
+ @servers.select {|s| s.openvpn_server? }[0].external_ip_addr if @servers.size > 0
77
+ end
78
+
79
+ def ssh_public_keys
80
+ @ssh_public_keys
81
+ end
82
+
83
+ # generate a Server Group XML from server_group.json
84
+ def self.from_json(json)
85
+
86
+ json_hash=JSON.parse(json)
87
+
88
+ sg=ServerGroup.new(
89
+ :name => json_hash["name"],
90
+ :description => json_hash["description"],
91
+ :domain_name => json_hash["domain_name"],
92
+ :vpn_device => json_hash["vpn_device"],
93
+ :vpn_proto => json_hash["vpn_proto"],
94
+ :vpn_network => json_hash["vpn_network"],
95
+ :vpn_subnet => json_hash["vpn_subnet"]
96
+ )
97
+ json_hash["servers"].each_pair do |server_name, server_config|
98
+ sg.servers << Server.new(
99
+ :name => server_name,
100
+ :description => server_config["description"],
101
+ :flavor_id => server_config["flavor_id"],
102
+ :image_id => server_config["image_id"],
103
+ :openvpn_server => server_config["openvpn_server"]
104
+ )
105
+ end
106
+
107
+ # automatically add a key for the current user
108
+ sg.ssh_public_keys << SshPublicKey.new(
109
+ :description => "#{ENV['USER']}'s public key",
110
+ :public_key => Util.load_public_key
111
+
112
+ )
113
+
114
+ return sg
115
+
116
+ end
117
+
118
+ def to_xml
119
+
120
+ xml = Builder::XmlMarkup.new
121
+ xml.tag! "server-group" do |sg|
122
+ sg.id(@id)
123
+ sg.name(@name)
124
+ sg.description(@description)
125
+ sg.tag! "owner-name", @owner_name
126
+ sg.tag! "domain-name", @domain_name
127
+ sg.tag! "vpn-device", @vpn_device if @vpn_device != "tun"
128
+ sg.tag! "vpn-proto", @vpn_proto if @vpn_proto != "tcp"
129
+ sg.tag! "vpn-network", @vpn_network
130
+ sg.tag! "vpn-subnet", @vpn_subnet
131
+ sg.servers("type" => "array") do |xml_servers|
132
+ self.servers.each do |server|
133
+ xml_servers.server do |xml_server|
134
+ xml_server.id(server.id)
135
+ xml_server.name(server.name)
136
+ xml_server.description(server.description)
137
+ xml_server.tag! "flavor-id", server.flavor_id
138
+ xml_server.tag! "image-id", server.image_id
139
+ if server.admin_password then
140
+ xml_server.tag! "admin-password", server.admin_password
141
+ end
142
+ xml_server.tag! "cloud-server-id-number", server.cloud_server_id_number if server.cloud_server_id_number
143
+ xml_server.tag! "status", server.status if server.status
144
+ xml_server.tag! "external-ip-addr", server.external_ip_addr if server.external_ip_addr
145
+ xml_server.tag! "internal-ip-addr", server.internal_ip_addr if server.internal_ip_addr
146
+ xml_server.tag! "error-message", server.error_message if server.error_message
147
+ xml_server.tag! "retry-count", server.retry_count if server.retry_count
148
+ if server.openvpn_server?
149
+ xml_server.tag! "openvpn-server", "true", { "type" => "boolean"}
150
+ end
151
+ end
152
+ end
153
+ end
154
+ sg.tag! "ssh-public-keys", { "type" => "array"} do |xml_ssh_pub_keys|
155
+ self.ssh_public_keys.each do |ssh_public_key|
156
+ xml_ssh_pub_keys.tag! "ssh-public-key" do |xml_ssh_pub_key|
157
+ xml_ssh_pub_key.description ssh_public_key.description
158
+ xml_ssh_pub_key.tag! "public-key", ssh_public_key.public_key
159
+ end
160
+ end
161
+ end
162
+ sg.tag! "clients", { "type" => "array"} do |xml_clients|
163
+ self.clients.each do |client|
164
+ xml_clients.tag! "client" do |xml_client|
165
+ xml_client.id client.id
166
+ xml_client.name client.name
167
+ xml_client.description client.description
168
+ xml_client.status client.status
169
+ end
170
+ end
171
+ end
172
+
173
+ end
174
+ xml.target!
175
+
176
+ end
177
+
178
+ def self.from_xml(xml)
179
+
180
+ sg=nil
181
+ dom = REXML::Document.new(xml)
182
+ REXML::XPath.each(dom, "/server-group") do |sg_xml|
183
+ sg=ServerGroup.new(
184
+ :name => XMLUtil.element_text(sg_xml, "name"),
185
+ :id => XMLUtil.element_text(sg_xml, "id").to_i,
186
+ :owner_name => XMLUtil.element_text(sg_xml, "owner-name"),
187
+ :domain_name => XMLUtil.element_text(sg_xml, "domain-name"),
188
+ :description => XMLUtil.element_text(sg_xml, "description"),
189
+ :vpn_device => XMLUtil.element_text(sg_xml, "vpn-device"),
190
+ :vpn_proto => XMLUtil.element_text(sg_xml, "vpn-proto"),
191
+ :vpn_network => XMLUtil.element_text(sg_xml, "vpn-network"),
192
+ :vpn_subnet => XMLUtil.element_text(sg_xml, "vpn-subnet")
193
+ )
194
+ REXML::XPath.each(dom, "//server") do |server_xml|
195
+
196
+ server=Server.new(
197
+ :id => XMLUtil.element_text(server_xml, "id").to_i,
198
+ :name => XMLUtil.element_text(server_xml, "name"),
199
+ :cloud_server_id_number => XMLUtil.element_text(server_xml, "cloud-server-id-number"),
200
+ :status => XMLUtil.element_text(server_xml, "status"),
201
+ :external_ip_addr => XMLUtil.element_text(server_xml, "external-ip-addr"),
202
+ :internal_ip_addr => XMLUtil.element_text(server_xml, "internal-ip-addr"),
203
+ :error_message => XMLUtil.element_text(server_xml, "error-message"),
204
+ :image_id => XMLUtil.element_text(server_xml, "image-id"),
205
+ :admin_password => XMLUtil.element_text(server_xml, "admin-password"),
206
+ :flavor_id => XMLUtil.element_text(server_xml, "flavor-id"),
207
+ :retry_count => XMLUtil.element_text(server_xml, "retry-count"),
208
+ :openvpn_server => XMLUtil.element_text(server_xml, "openvpn-server")
209
+ )
210
+ sg.servers << server
211
+ end
212
+ REXML::XPath.each(dom, "//client") do |client_xml|
213
+
214
+ client=Client.new(
215
+ :id => XMLUtil.element_text(client_xml, "id").to_i,
216
+ :name => XMLUtil.element_text(client_xml, "name"),
217
+ :description => XMLUtil.element_text(client_xml, "description"),
218
+ :status => XMLUtil.element_text(client_xml, "status")
219
+ )
220
+ sg.clients << client
221
+ end
222
+
223
+ end
224
+
225
+ sg
226
+
227
+ end
228
+
229
+ def pretty_print
230
+
231
+ puts "Group ID: #{@id}"
232
+ puts "name: #{@name}"
233
+ puts "description: #{@description}"
234
+ puts "domain name: #{@domain_name}"
235
+ puts "VPN gateway IP: #{self.gateway_ip}"
236
+ puts "Servers:"
237
+ servers.each do |server|
238
+ puts "\tname: #{server.name} (id: #{server.id})"
239
+ puts "\tstatus: #{server.status}"
240
+ if server.openvpn_server?
241
+ puts "\tOpenVPN server: #{server.openvpn_server?}"
242
+ end
243
+ if server.error_message then
244
+ puts "\tlast error message: #{server.error_message}"
245
+ end
246
+ puts "\t--"
247
+ end
248
+
249
+ end
250
+
251
+ def server_names
252
+
253
+ names=[]
254
+
255
+ servers.each do |server|
256
+ if block_given? then
257
+ yield server.name
258
+ else
259
+ names << server.name
260
+ end
261
+ end
262
+
263
+ names
264
+
265
+ end
266
+
267
+ def cache_to_disk
268
+ FileUtils.mkdir_p(@@data_dir)
269
+ File.open(File.join(@@data_dir, "#{@id}.xml"), 'w') do |f|
270
+ f.chmod(0600)
271
+ f.write(self.to_xml)
272
+ end
273
+ end
274
+
275
+ def delete
276
+ Connection.delete("/server_groups/#{@id}.xml")
277
+ out_file=File.join(@@data_dir, "#{@id}.xml")
278
+ File.delete(out_file) if File.exists?(out_file)
279
+ end
280
+
281
+ # Poll the server group until it is online.
282
+ # :timeout - max number of seconds to wait before raising an exception.
283
+ # Defaults to 1500
284
+ def poll_until_online(options={})
285
+
286
+ timeout=options[:timeout] or timeout = ENV['TIMEOUT']
287
+ if timeout.nil? or timeout.empty? then
288
+ timeout=1500 # defaults to 25 minutes
289
+ end
290
+
291
+ online = false
292
+ count=0
293
+ until online or (count*20) >= timeout.to_i do
294
+ count+=1
295
+ begin
296
+ sg=ServerGroup.get(:id => @id, :source => "remote")
297
+
298
+ online=true
299
+ sg.servers.each do |server|
300
+ if ["Pending", "Rebuilding"].include?(server.status) then
301
+ online=false
302
+ end
303
+ if server.status == "Failed" then
304
+ raise "Failed to create server group with the following message: #{server.error_message}"
305
+ end
306
+ end
307
+ if not online
308
+ yield sg if block_given?
309
+ sleep 20
310
+ end
311
+ rescue EOFError
312
+ end
313
+ end
314
+ if (count*20) >= timeout.to_i then
315
+ raise "Timeout waiting for server groups to come online."
316
+ end
317
+
318
+ end
319
+
320
+ def self.create(sg)
321
+
322
+ xml=Connection.post("/server_groups.xml", sg.to_xml)
323
+ sg=ServerGroup.from_xml(xml)
324
+
325
+ old_group_xml=nil
326
+ vpn_gateway=nil
327
+ sg.poll_until_online do |server_group|
328
+ if old_group_xml != server_group.to_xml then
329
+ old_group_xml = server_group.to_xml
330
+ vpn_gateway = server_group.gateway_ip if server_group.gateway_ip
331
+ if not vpn_gateway.nil? and not vpn_gateway.empty? then
332
+ SshUtil.remove_known_hosts_ip(vpn_gateway)
333
+ end
334
+ server_group.pretty_print
335
+ end
336
+ end
337
+ sg=ServerGroup.get(:id => sg.id, :source => "remote")
338
+ sg.cache_to_disk
339
+ puts "Server group online."
340
+ sg
341
+
342
+ end
343
+
344
+ # Get a server group. The following options are available:
345
+ #
346
+ # :id - The ID of the server group to get. Defaults to ENV['GROUP_ID']
347
+ # :source - valid options are 'cache' and 'remote'
348
+ def self.get(options={})
349
+
350
+ source = options[:source] or source = "cache"
351
+ id = options[:id]
352
+ if id.nil? then
353
+ group = ServerGroup.most_recent
354
+ raise "No recent server group files exist." if group.nil?
355
+ id = group.id
356
+ end
357
+
358
+ if source == "remote" then
359
+ xml=Connection.get("/server_groups/#{id}.xml")
360
+ ServerGroup.from_xml(xml)
361
+ elsif source == "cache" then
362
+ out_file = File.join(@@data_dir, "#{id}.xml")
363
+ raise "No server group files exist." if not File.exists?(out_file)
364
+ ServerGroup.from_xml(IO.read(out_file))
365
+ else
366
+ raise "Invalid get :source specified."
367
+ end
368
+
369
+ end
370
+
371
+ # :source - valid options are 'remote' and 'cache'
372
+ def self.index(options={})
373
+
374
+ source = options[:source] or source = "cache"
375
+ server_groups=[]
376
+ Dir[File.join(ServerGroup.data_dir, '*.xml')].each do |file|
377
+ server_groups << ServerGroup.from_xml(IO.read(file))
378
+ end
379
+ server_groups
380
+
381
+ end
382
+
383
+ def self.most_recent
384
+ server_groups=[]
385
+ Dir[File.join(@@data_dir, "*.xml")].each do |file|
386
+ server_groups << ServerGroup.from_xml(IO.read(file))
387
+ end
388
+ if server_groups.size > 0 then
389
+ return server_groups.sort { |a,b| b.id <=> a.id }[0]
390
+ else
391
+ nil
392
+ end
393
+ end
394
+
395
+ end
396
+
397
+ end
398
+
399
+ end
400
+
401
+ end