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