Class: Sumo

Inherits:
Object
  • Object
show all
Defined in:
lib/sumo.rb

Instance Method Summary collapse

Instance Method Details

#attach(volume, instance, device) ⇒ Object



56
57
58
59
60
61
62
63
# File 'lib/sumo.rb', line 56

def attach(volume, instance, device)
	result = ec2.attach_volume(
		:volume_id => volume,
		:instance_id => instance,
		:device => device
	)
	"done"
end

#attached_volumesObject



48
49
50
# File 'lib/sumo.rb', line 48

def attached_volumes
	volumes.select { |vol| vol[:status] == 'in-use' }
end

#available_volumesObject



44
45
46
# File 'lib/sumo.rb', line 44

def available_volumes
	volumes.select { |vol| vol[:status] == 'available' }
end

#bootstrap_chef(hostname) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
# File 'lib/sumo.rb', line 162

def bootstrap_chef(hostname)
	commands = [
		'apt-get update',
		'apt-get autoremove -y',
		'apt-get install -y ruby ruby-dev rubygems git-core',
		'gem sources -a http://gems.opscode.com',
		'gem install chef ohai --no-rdoc --no-ri',
		"git clone #{config['cookbooks_url']}",
	]
	ssh(hostname, commands)
end

#configObject



217
218
219
# File 'lib/sumo.rb', line 217

def config
	@config ||= default_config.merge read_config
end

#console_output(instance_id) ⇒ Object



213
214
215
# File 'lib/sumo.rb', line 213

def console_output(instance_id)
	ec2.get_console_output(:instance_id => instance_id)["output"]
end

#create_keypairObject



243
244
245
246
247
# File 'lib/sumo.rb', line 243

def create_keypair
	keypair = ec2.create_keypair(:key_name => "sumo").keyMaterial
	File.open(keypair_file, 'w') { |f| f.write keypair }
	File.chmod 0600, keypair_file
end

#create_security_groupObject



249
250
251
252
# File 'lib/sumo.rb', line 249

def create_security_group
	ec2.create_security_group(:group_name => 'sumo', :group_description => 'Sumo')
rescue AWS::InvalidGroupDuplicate
end

#create_volume(size) ⇒ Object



70
71
72
73
74
75
76
# File 'lib/sumo.rb', line 70

def create_volume(size)
	result = ec2.create_volume(
		:availability_zone => config['availability_zone'],
		:size => size.to_s
	)
	result["volumeId"]
end

#default_configObject



221
222
223
224
225
226
227
# File 'lib/sumo.rb', line 221

def default_config
	{
		'user' => 'root',
		'ami' => 'ami-ed46a784',
		'availability_zone' => 'us-east-1b'
	}
end

#destroy_volume(volume) ⇒ Object



78
79
80
81
# File 'lib/sumo.rb', line 78

def destroy_volume(volume)
	ec2.delete_volume(:volume_id => volume)
	"done"
end

#detach(volume) ⇒ Object



65
66
67
68
# File 'lib/sumo.rb', line 65

def detach(volume)
	result = ec2.detach_volume(:volume_id => volume, :force => "true")
	"done"
end

#ec2Object



265
266
267
# File 'lib/sumo.rb', line 265

def ec2
	@ec2 ||= AWS::EC2::Base.new(:access_key_id => config['access_id'], :secret_access_key => config['access_secret'])
end

#fetch_listObject



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/sumo.rb', line 83

def fetch_list
	result = ec2.describe_instances
	return [] unless result.reservationSet

	instances = []
	result.reservationSet.item.each do |r|
		r.instancesSet.item.each do |item|
			instances << {
				:instance_id => item.instanceId,
				:status => item.instanceState.name,
				:hostname => item.dnsName
			}
		end
	end
	instances
end

#fetch_resources(hostname) ⇒ Object



196
197
198
199
200
201
# File 'lib/sumo.rb', line 196

def fetch_resources(hostname)
	cmd = "ssh -i #{keypair_file} #{config['user']}@#{hostname} 'cat /root/resources' 2>&1"
	out = IO.popen(cmd, 'r') { |pipe| pipe.read }
	abort "failed to read resources, output:\n#{out}" unless $?.success?
	parse_resources(out, hostname)
end

#find(id_or_hostname) ⇒ Object



100
101
102
103
104
105
106
107
108
# File 'lib/sumo.rb', line 100

def find(id_or_hostname)
	return unless id_or_hostname
	id_or_hostname = id_or_hostname.strip.downcase
	list.detect do |inst|
		inst[:hostname] == id_or_hostname or
		inst[:instance_id] == id_or_hostname or
		inst[:instance_id].gsub(/^i-/, '') == id_or_hostname
	end
end

#find_volume(volume_id) ⇒ Object



110
111
112
113
114
115
116
117
# File 'lib/sumo.rb', line 110

def find_volume(volume_id)
	return unless volume_id
	volume_id = volume_id.strip.downcase
	volumes.detect do |volume|
		volume[:volume_id] == volume_id or
		volume[:volume_id].gsub(/^vol-/, '') == volume_id
	end
end

#instance_info(instance_id) ⇒ Object



131
132
133
134
135
# File 'lib/sumo.rb', line 131

def instance_info(instance_id)
	fetch_list.detect do |inst|
		inst[:instance_id] == instance_id
	end
end

#keypair_fileObject



239
240
241
# File 'lib/sumo.rb', line 239

def keypair_file
	"#{sumo_dir}/keypair.pem"
end

#launchObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/sumo.rb', line 6

def launch
	ami = config['ami']
	raise "No AMI selected" unless ami

	create_keypair unless File.exists? keypair_file

	create_security_group
	open_firewall(22)

	result = ec2.run_instances(
		:image_id => ami,
		:instance_type => config['instance_size'] || 'm1.small',
		:key_name => 'sumo',
		:group_id => [ 'sumo' ],
		:availability_zone => config['availability_zone']
	)
	result.instancesSet.item[0].instanceId
end

#listObject



25
26
27
# File 'lib/sumo.rb', line 25

def list
	@list ||= fetch_list
end

#list_by_status(status) ⇒ Object



127
128
129
# File 'lib/sumo.rb', line 127

def list_by_status(status)
	list.select { |i| i[:status] == status }
end

#nondestroyed_volumesObject



52
53
54
# File 'lib/sumo.rb', line 52

def nondestroyed_volumes
	volumes.select { |vol| vol[:status] != 'deleting' }
end

#open_firewall(port) ⇒ Object



254
255
256
257
258
259
260
261
262
263
# File 'lib/sumo.rb', line 254

def open_firewall(port)
	ec2.authorize_security_group_ingress(
		:group_name => 'sumo',
		:ip_protocol => 'tcp',
		:from_port => port,
		:to_port => port,
		:cidr_ip => '0.0.0.0/0'
	)
rescue AWS::InvalidPermissionDuplicate
end

#parse_resources(raw, hostname) ⇒ Object



203
204
205
206
207
# File 'lib/sumo.rb', line 203

def parse_resources(raw, hostname)
	raw.split("\n").map do |line|
		line.gsub(/localhost/, hostname)
	end
end

#pendingObject



123
124
125
# File 'lib/sumo.rb', line 123

def pending
	list_by_status('pending')
end

#read_configObject



233
234
235
236
237
# File 'lib/sumo.rb', line 233

def read_config
	YAML.load File.read("#{sumo_dir}/config.yml")
rescue Errno::ENOENT
	raise "Sumo is not configured, please fill in ~/.sumo/config.yml"
end

#resources(hostname) ⇒ Object



191
192
193
194
# File 'lib/sumo.rb', line 191

def resources(hostname)
	@resources ||= {}
	@resources[hostname] ||= fetch_resources(hostname)
end

#runningObject



119
120
121
# File 'lib/sumo.rb', line 119

def running
	list_by_status('running')
end

#setup_role(hostname, role) ⇒ Object



174
175
176
177
178
179
180
# File 'lib/sumo.rb', line 174

def setup_role(hostname, role)
	commands = [
		"cd chef-cookbooks",
		"/var/lib/gems/1.8/bin/chef-solo -c config.json -j roles/#{role}.json"
	]
	ssh(hostname, commands)
end

#ssh(hostname, cmds) ⇒ Object



182
183
184
185
186
187
188
189
# File 'lib/sumo.rb', line 182

def ssh(hostname, cmds)
	IO.popen("ssh -i #{keypair_file} #{config['user']}@#{hostname} > ~/.sumo/ssh.log 2>&1", "w") do |pipe|
		pipe.puts cmds.join(' && ')
	end
	unless $?.success?
		abort "failed\nCheck ~/.sumo/ssh.log for the output"
	end
end

#sumo_dirObject



229
230
231
# File 'lib/sumo.rb', line 229

def sumo_dir
	"#{ENV['HOME']}/.sumo"
end

#terminate(instance_id) ⇒ Object



209
210
211
# File 'lib/sumo.rb', line 209

def terminate(instance_id)
	ec2.terminate_instances(:instance_id => [ instance_id ])
end

#volumesObject



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/sumo.rb', line 29

def volumes
	result = ec2.describe_volumes
	return [] unless result.volumeSet

	result.volumeSet.item.map do |row|
		{
			:volume_id => row["volumeId"],
			:size => row["size"],
			:status => row["status"],
			:device => (row["attachmentSet"]["item"].first["device"] rescue ""),
			:instance_id => (row["attachmentSet"]["item"].first["instanceId"] rescue ""),
		}
	end
end

#wait_for_hostname(instance_id) ⇒ Object

Raises:

  • (ArgumentError)


137
138
139
140
141
142
143
144
145
146
147
# File 'lib/sumo.rb', line 137

def wait_for_hostname(instance_id)
	raise ArgumentError unless instance_id and instance_id.match(/^i-/)
	loop do
		if inst = instance_info(instance_id)
			if hostname = inst[:hostname]
				return hostname
			end
		end
		sleep 1
	end
end

#wait_for_ssh(hostname) ⇒ Object

Raises:

  • (ArgumentError)


149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/sumo.rb', line 149

def wait_for_ssh(hostname)
	raise ArgumentError unless hostname
	loop do
		begin
			Timeout::timeout(4) do
				TCPSocket.new(hostname, 22)
				return
			end
		rescue SocketError, Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH
		end
	end
end