cluster 0.5.33
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/Rakefile +69 -0
- data/VERSION +1 -0
- data/bin/cluster +14 -0
- data/bin/ec2-consistent-snapshot +676 -0
- data/bin/periodic.sh +19 -0
- data/examples/cacerts.pem +19 -0
- data/examples/credentials.yml +24 -0
- data/examples/monitor.god +88 -0
- data/examples/users.sh +42 -0
- data/lib/cluster.rb +267 -0
- data/lib/cluster/cli.rb +206 -0
- data/lib/cluster/configuration.rb +52 -0
- data/lib/cluster/infrastructure.rb +160 -0
- data/lib/cluster/infrastructures/amazon.rb +568 -0
- data/lib/cluster/infrastructures/amazon_instance.rb +270 -0
- data/lib/cluster/infrastructures/amazon_release.rb +63 -0
- data/lib/cluster/instance.rb +97 -0
- data/lib/cluster/release.rb +30 -0
- data/lib/cluster/version.rb +25 -0
- data/lib/ext/array.rb +13 -0
- data/lib/ext/cluster_extensions.rb +13 -0
- metadata +206 -0
@@ -0,0 +1,270 @@
|
|
1
|
+
class AmazonInstance < Instance
|
2
|
+
attr_accessor :ssh_key_name,
|
3
|
+
:private_dns_name,
|
4
|
+
:dns_name,
|
5
|
+
:aws_image_id,
|
6
|
+
:spot_price,
|
7
|
+
:aws_groups,
|
8
|
+
:aws_availability_zone,
|
9
|
+
:aws_state,
|
10
|
+
:aws_launch_time,
|
11
|
+
:start_time_sorted,
|
12
|
+
:aws_id,
|
13
|
+
:aws_instance_id,
|
14
|
+
:aws_image_id,
|
15
|
+
:aws_instance_type
|
16
|
+
|
17
|
+
def identified_by?(arg)
|
18
|
+
arg = arg.downcase
|
19
|
+
super(arg) or @private_dns_name == arg or @dns_name == arg
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop!
|
23
|
+
if amazon.ecc.terminate_instances(ec2_id)
|
24
|
+
amazon.sdb.delete_attributes amazon.domain, aws_id
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def dns
|
29
|
+
if Infrastructure.in_cluster?
|
30
|
+
private_dns_name
|
31
|
+
else
|
32
|
+
dns_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def ip
|
37
|
+
Infrastructure.dns.getaddress(dns).to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def id
|
41
|
+
@aws_instance_id
|
42
|
+
end
|
43
|
+
alias :ec2_id :id
|
44
|
+
|
45
|
+
alias :groups :aws_groups
|
46
|
+
|
47
|
+
def no_sdb?
|
48
|
+
!@set_sdb
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_sdb_attributes(args)
|
52
|
+
args.each do |k, v|
|
53
|
+
case k
|
54
|
+
when 'start_time_sorted'
|
55
|
+
self.start_time = Time.parse v
|
56
|
+
when 'entry', 'ec2_id'
|
57
|
+
# NOP
|
58
|
+
when 'services'
|
59
|
+
@services = v.is_a?(Array) ? v : Array(v)
|
60
|
+
when 'disabled_services'
|
61
|
+
@disabled_services = v.is_a?(Array) ? v : Array(v)
|
62
|
+
else
|
63
|
+
self.send("#{k}=", v)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
@set_sdb = true
|
67
|
+
end
|
68
|
+
|
69
|
+
def ec2_id=(id)
|
70
|
+
aws_instance_id = id
|
71
|
+
end
|
72
|
+
|
73
|
+
def aws_id
|
74
|
+
@aws_id ||= UUIDTools::UUID.timestamp_create.to_s
|
75
|
+
end
|
76
|
+
|
77
|
+
def start_time_sorted
|
78
|
+
start_time.xmlschema(3)
|
79
|
+
end
|
80
|
+
|
81
|
+
def fields
|
82
|
+
%w(aws_id label start_time_sorted spot_price services disabled_services friendly_name ec2_id state)
|
83
|
+
end
|
84
|
+
|
85
|
+
def attributes
|
86
|
+
args = fields.inject({'entry' => 'machine'}) {|m, attr|
|
87
|
+
m.merge attr => self.send(attr)
|
88
|
+
}
|
89
|
+
Amazon.to_sdb_attributes(args)
|
90
|
+
end
|
91
|
+
|
92
|
+
def start!(price = nil)
|
93
|
+
args = {
|
94
|
+
:key_name => key,
|
95
|
+
:user_data => user_data,
|
96
|
+
:instance_type => type,
|
97
|
+
:availability_zone => zone,
|
98
|
+
}
|
99
|
+
|
100
|
+
resp = if spot_price
|
101
|
+
args.merge! :spot_price => spot_price,
|
102
|
+
:instance_count => 1,
|
103
|
+
:groups => groups,
|
104
|
+
:image_id => image
|
105
|
+
amazon.ecc.request_spot_instances args
|
106
|
+
else
|
107
|
+
args.merge! :group_ids => groups,
|
108
|
+
:min_count => 1,
|
109
|
+
:max_count => 1
|
110
|
+
amazon.ecc.launch_instances image, args
|
111
|
+
end
|
112
|
+
|
113
|
+
if resp
|
114
|
+
puts "INS RESP -> #{resp.inspect}"
|
115
|
+
resp.first.keys.each do |k|
|
116
|
+
func = "#{k.to_s}="
|
117
|
+
send(func, resp.first[k]) if respond_to? func
|
118
|
+
end
|
119
|
+
|
120
|
+
res = amazon.sdb.put_attributes(amazon.domain, aws_id, attributes, :replace)
|
121
|
+
puts "Res #{res.inspect}"
|
122
|
+
self
|
123
|
+
else
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def groups
|
129
|
+
return @aws_groups if @aws_groups
|
130
|
+
|
131
|
+
groups = services.map {|s|
|
132
|
+
grps = options.services_to_groups
|
133
|
+
if grps and grps.include? s
|
134
|
+
grps
|
135
|
+
else
|
136
|
+
s
|
137
|
+
end
|
138
|
+
}.flatten
|
139
|
+
(groups << 'default').uniq
|
140
|
+
end
|
141
|
+
|
142
|
+
def key
|
143
|
+
groups.include?('access') ? 'access' : 'cloud'
|
144
|
+
end
|
145
|
+
|
146
|
+
def user_data
|
147
|
+
%Q@#!/bin/bash
|
148
|
+
|
149
|
+
CLUSTER_USER=ubuntu
|
150
|
+
CLUSTER_DIR=/home/${CLUSTER_USER}/.cluster
|
151
|
+
LOGGER=${CLUSTER_DIR}/cluster.log
|
152
|
+
MONITOR=${CLUSTER_DIR}/monitor.god
|
153
|
+
CREDENTIALS=${CLUSTER_DIR}/credentials.yml
|
154
|
+
CLUSTER_EXECUTABLE=$(which cluster)
|
155
|
+
GEM_PATH=$(gem env gempath)
|
156
|
+
GEM_BIN_DIR=$(dirname ${CLUSTER_EXECUTABLE})
|
157
|
+
GOD=/usr/bin/god
|
158
|
+
|
159
|
+
if [[ ! -e $CLUSTER_DIR ]]; then
|
160
|
+
mkdir -m 700 $CLUSTER_DIR
|
161
|
+
fi
|
162
|
+
|
163
|
+
if [[ ! -e $CREDENTIALS ]]; then
|
164
|
+
wget --no-check-certificate -O $CREDENTIALS '#{amazon.credentials_url}'
|
165
|
+
fi
|
166
|
+
|
167
|
+
if [[ ! -e $CREDENTIALS ]]; then
|
168
|
+
echo "Cannot start without credentials! (perhaps a cluster save_credentials is in order)"
|
169
|
+
exit 1
|
170
|
+
fi
|
171
|
+
|
172
|
+
chmod 600 $CREDENTIALS
|
173
|
+
chown $CLUSTER_USER $CREDENTIALS
|
174
|
+
cat >>$CREDENTIALS <<EOF
|
175
|
+
cluster:
|
176
|
+
id: #{aws_id}
|
177
|
+
services: #{services.join(' ')}
|
178
|
+
EOF
|
179
|
+
|
180
|
+
wget --no-check-certificate -O /tmp/cluster.gem '#{Cluster::LOCATION}'
|
181
|
+
if [[ -e /tmp/cluster.gem ]]; then
|
182
|
+
if [[ $CLUSTER_EXECUTABLE != '' ]]; then
|
183
|
+
gem uninstall -x cluster
|
184
|
+
fi
|
185
|
+
gem install /tmp/cluster.gem
|
186
|
+
rm /tmp/cluster.gem
|
187
|
+
fi
|
188
|
+
|
189
|
+
|
190
|
+
CLUSTER='X'
|
191
|
+
if type -P cluster >/dev/null; then
|
192
|
+
CLUSTER=cluster
|
193
|
+
elif [[ -x $GEM_BIN_DIR/cluster ]]; then
|
194
|
+
CLUSTER="$GEM_BIN_DIR/cluster"
|
195
|
+
fi
|
196
|
+
|
197
|
+
if [[ ! $CLUSTER == 'X' ]]; then
|
198
|
+
CLUSTER="$CLUSTER --credentials=${CREDENTIALS} --logger=${LOGGER}"
|
199
|
+
$CLUSTER instance_state starting
|
200
|
+
|
201
|
+
$CLUSTER fetch_monitor ${MONITOR}
|
202
|
+
if [[ -e $MONITOR ]]; then
|
203
|
+
chmod 600 $MONITOR
|
204
|
+
chown $CLUSTER_USER $MONITOR
|
205
|
+
mv $MONITOR /etc/god/init.d/cluster.god
|
206
|
+
$GOD load /etc/god/init.d/cluster.god
|
207
|
+
else
|
208
|
+
echo "NO MONITOR FILE!"
|
209
|
+
exit 1
|
210
|
+
fi
|
211
|
+
else
|
212
|
+
echo "NO CLUSTER!"
|
213
|
+
exit 1
|
214
|
+
fi
|
215
|
+
|
216
|
+
chown -R $CLUSTER_USER:$CLUSTER_USER $CLUSTER_DIR
|
217
|
+
@.gsub(/^ /, '')
|
218
|
+
end
|
219
|
+
|
220
|
+
def set_state(state)
|
221
|
+
amazon.sdb.put_attributes amazon.domain, aws_id, Amazon.to_sdb_attributes(:state => state), :replace
|
222
|
+
@state = state
|
223
|
+
end
|
224
|
+
|
225
|
+
def options
|
226
|
+
amazon.options
|
227
|
+
end
|
228
|
+
|
229
|
+
def zone
|
230
|
+
amazon.options.zone
|
231
|
+
end
|
232
|
+
|
233
|
+
def amazon
|
234
|
+
Infrastructure.current
|
235
|
+
end
|
236
|
+
|
237
|
+
def image
|
238
|
+
return @aws_image_id if @aws_image_id
|
239
|
+
|
240
|
+
case type
|
241
|
+
when 'm1.small', 'c1.medium'
|
242
|
+
amazon.get_image 32
|
243
|
+
else
|
244
|
+
amazon.get_image 64
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def type
|
249
|
+
return @aws_instance_type if @aws_instance_type
|
250
|
+
|
251
|
+
@aws_instance_type = self.class.size_to_type size
|
252
|
+
end
|
253
|
+
|
254
|
+
class << self
|
255
|
+
def size_to_type(size)
|
256
|
+
case size
|
257
|
+
when 'minimum', 'basic'
|
258
|
+
'm1.small'
|
259
|
+
when 'average'
|
260
|
+
'm1.large'
|
261
|
+
when 'power'
|
262
|
+
'c1.medium'
|
263
|
+
when 'super'
|
264
|
+
'c1.xlarge'
|
265
|
+
when /^\w+\.\w+$/
|
266
|
+
size
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class AmazonRelease < Release
|
2
|
+
attr_accessor :aws_id
|
3
|
+
|
4
|
+
def created_at
|
5
|
+
@created_at ||= Time.now
|
6
|
+
end
|
7
|
+
|
8
|
+
def sdb_created_at
|
9
|
+
created_at.xmlschema(3)
|
10
|
+
end
|
11
|
+
|
12
|
+
def sdb_created_at=(time)
|
13
|
+
self.created_at = Time.parse(time)
|
14
|
+
end
|
15
|
+
|
16
|
+
def amazon
|
17
|
+
Infrastructure.current
|
18
|
+
end
|
19
|
+
|
20
|
+
def save
|
21
|
+
fields = %w(aws_id label sdb_created_at environment tag)
|
22
|
+
args = fields.inject({'entry' => 'release'}) {|m, attr|
|
23
|
+
m.merge attr => self.send(attr)
|
24
|
+
}
|
25
|
+
attributes = Amazon.to_sdb_attributes(args)
|
26
|
+
amazon.sdb.put_attributes(amazon.domain, aws_id, attributes, :replace)
|
27
|
+
@new_release = true
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def new?
|
32
|
+
@new_release
|
33
|
+
end
|
34
|
+
|
35
|
+
def aws_id
|
36
|
+
@aws_id ||= UUIDTools::UUID.timestamp_create.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def create(*params)
|
41
|
+
self.new(*params).save
|
42
|
+
end
|
43
|
+
|
44
|
+
def current(environment = 'staging')
|
45
|
+
query_for_release "entry = 'release' AND environment = '#{environment}' AND sdb_created_at IS NOT NULL ORDER BY sdb_created_at DESC limit 1"
|
46
|
+
end
|
47
|
+
|
48
|
+
def find(env, tag)
|
49
|
+
query_for_release "entry = 'release' AND environment = '#{env}' AND tag = '#{tag}'"
|
50
|
+
end
|
51
|
+
|
52
|
+
def query_for_release(clause)
|
53
|
+
amazon = Infrastructure.current
|
54
|
+
res = amazon.sdb.select("SELECT * FROM #{amazon.domain} WHERE #{clause}")
|
55
|
+
args = Amazon.from_sdb_results(res)
|
56
|
+
if args.empty?
|
57
|
+
nil
|
58
|
+
else
|
59
|
+
self.new(args.first)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'ext/cluster_extensions.rb'
|
3
|
+
|
4
|
+
class Instance
|
5
|
+
attr_accessor :label,
|
6
|
+
:id,
|
7
|
+
:address,
|
8
|
+
:groups,
|
9
|
+
:services,
|
10
|
+
:disabled_services,
|
11
|
+
:size,
|
12
|
+
:state,
|
13
|
+
:start_time,
|
14
|
+
:friendly_name
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
options = args.extract_options!
|
18
|
+
options.each do |key, value|
|
19
|
+
func = "#{key}="
|
20
|
+
if self.respond_to? func
|
21
|
+
self.send func, value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def start_time
|
27
|
+
case @start_time
|
28
|
+
when Time
|
29
|
+
@start_time
|
30
|
+
when nil
|
31
|
+
@start_time = Time.now
|
32
|
+
else
|
33
|
+
@start_time = Time.parse @start_time
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def identified_by?(arg)
|
38
|
+
arg = arg.downcase
|
39
|
+
self.id == arg or self.label == arg or self.dns == arg or self.friendly_name == arg
|
40
|
+
end
|
41
|
+
|
42
|
+
def state
|
43
|
+
@state ||= 'spinning'
|
44
|
+
end
|
45
|
+
|
46
|
+
def services
|
47
|
+
@services ||= []
|
48
|
+
end
|
49
|
+
|
50
|
+
def disabled_services
|
51
|
+
@disabled_services ||= []
|
52
|
+
end
|
53
|
+
|
54
|
+
def enable(service_list)
|
55
|
+
for service in service_list
|
56
|
+
@disabled_services.delete(service) if disabled_services.include? service
|
57
|
+
|
58
|
+
unless services.include? service
|
59
|
+
@services = services << service
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@services
|
64
|
+
end
|
65
|
+
|
66
|
+
def disable(service_list)
|
67
|
+
for service in service_list
|
68
|
+
unless disabled_services.include? service
|
69
|
+
@disabled_services = disabled_services << service
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
@disabled_services
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s(format = nil)
|
77
|
+
case format
|
78
|
+
when :long
|
79
|
+
svs = if services.empty?
|
80
|
+
'N/S'
|
81
|
+
else
|
82
|
+
services.map {|s|
|
83
|
+
disabled_services.include?(s) ? "!#{s}" : s
|
84
|
+
}.join(',')
|
85
|
+
end
|
86
|
+
"#{friendly_name}\t#{svs}\t#{state}\t#{id}\t#{dns}"
|
87
|
+
else
|
88
|
+
label or id
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class << self
|
93
|
+
def create(*args)
|
94
|
+
new(*args).start!
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Release
|
2
|
+
attr_accessor :label,
|
3
|
+
:tag,
|
4
|
+
:environment,
|
5
|
+
:created_at
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
options = args.extract_options!
|
9
|
+
options.each do |key, value|
|
10
|
+
func = "#{key}="
|
11
|
+
if self.respond_to? func
|
12
|
+
self.send func, value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def environment
|
18
|
+
@environment ||= 'staging'
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def current(environment = 'staging')
|
23
|
+
Infrastructure.current.release_class.current(environment)
|
24
|
+
end
|
25
|
+
|
26
|
+
def find(env, tag)
|
27
|
+
Infrastructure.current.release_class.find(environment, tag)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|