ey_stonith 0.3.6 → 0.4.1.pre

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 (42) hide show
  1. data/.gitignore +10 -0
  2. data/Gemfile +3 -0
  3. data/Rakefile +26 -0
  4. data/ey_stonith.gemspec +36 -0
  5. data/features/check.feature +57 -0
  6. data/features/cron.feature +67 -0
  7. data/features/fixtures/dead.ru +6 -0
  8. data/features/fixtures/healthy.ru +1 -0
  9. data/features/help.feature +7 -0
  10. data/features/not_found.feature +24 -0
  11. data/features/notify.feature +35 -0
  12. data/features/resume.feature +49 -0
  13. data/features/steps/stonith_steps.rb +40 -0
  14. data/features/stop.feature +48 -0
  15. data/features/support/env.rb +77 -0
  16. data/lib/ey_stonith/awsm_notifier.rb +13 -8
  17. data/lib/ey_stonith/commands/abstract.rb +0 -4
  18. data/lib/ey_stonith/commands/check.rb +13 -36
  19. data/lib/ey_stonith/commands/claim.rb +5 -96
  20. data/lib/ey_stonith/commands/cron.rb +3 -5
  21. data/lib/ey_stonith/commands/info.rb +1 -4
  22. data/lib/ey_stonith/commands/not_found.rb +1 -0
  23. data/lib/ey_stonith/commands/notify.rb +16 -55
  24. data/lib/ey_stonith/commands/reset.rb +0 -1
  25. data/lib/ey_stonith/commands/stop.rb +0 -1
  26. data/lib/ey_stonith/commands/takeover.rb +5 -90
  27. data/lib/ey_stonith/commands.rb +1 -1
  28. data/lib/ey_stonith/config.rb +17 -66
  29. data/lib/ey_stonith/rackapp.rb +26 -2
  30. data/lib/ey_stonith.rb +8 -15
  31. data/spec/config_spec.rb +53 -0
  32. data/spec/fixtures/config.yml +11 -0
  33. data/spec/fixtures/empty.yml +1 -0
  34. data/spec/helpers.rb +15 -0
  35. data/spec/history_spec.rb +58 -0
  36. data/spec/rackapp_spec.rb +100 -0
  37. data/spec/spec_helper.rb +24 -0
  38. metadata +240 -60
  39. data/lib/ey_stonith/address_stealer.rb +0 -40
  40. data/lib/ey_stonith/check_recorder.rb +0 -55
  41. data/lib/ey_stonith/data.rb +0 -11
  42. data/lib/ey_stonith/database.rb +0 -78
@@ -0,0 +1,11 @@
1
+ ---
2
+ log: var/log/stonith.log
3
+ state_dir: var/run/stonith
4
+ success_path: var/run/stonith/success
5
+ heartbeat: 1
6
+ endpoint_uri: http://example.com/stonith
7
+ endpoint_token: i-87654321
8
+ endpoint_id: i-87654321
9
+ monitor_host: localhost
10
+ monitor_path: /chickity/check
11
+ monitor_timeout: 10
@@ -0,0 +1 @@
1
+ --- {}
data/spec/helpers.rb ADDED
@@ -0,0 +1,15 @@
1
+ module Helpers
2
+ def start_server(config)
3
+ config_ru = File.dirname(__FILE__) + "/fixtures/#{config}.ru"
4
+ @server = RealWeb.start_server(config_ru)
5
+ server_hostname
6
+ end
7
+
8
+ def stop_server
9
+ @server.stop
10
+ end
11
+
12
+ def server_hostname
13
+ "127.0.0.1:#{@server.port}"
14
+ end
15
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe EY::Stonith::History do
4
+ let(:path) { Pathname.new('tmp/history') }
5
+ before { path.delete if path.exist? }
6
+ subject { described_class.new(path) }
7
+
8
+ it "fails hard if is not a true history" do
9
+ lambda { described_class.new(path, 1) }.should raise_error
10
+ end
11
+
12
+ context "with 1 entry" do
13
+ before { subject << :one }
14
+
15
+ it "has that entry without arrows" do
16
+ subject.to_s.should == "one"
17
+ end
18
+
19
+ it "resets" do
20
+ subject.reset
21
+ subject.to_s.should == ""
22
+ end
23
+
24
+ it "doesn't duplicate entries" do
25
+ subject << :one
26
+ subject.to_s.should == "one"
27
+ end
28
+ end
29
+
30
+ context "with 2 entries" do
31
+ before { subject << :one << :two }
32
+
33
+ it "has both entries connected by an arrow" do
34
+ subject.to_s.should == "one -> two"
35
+ end
36
+
37
+ it "doesn't scrub duplicates that aren't adjacent" do
38
+ subject << :one
39
+ subject.to_s.should == "two -> one"
40
+ end
41
+ end
42
+
43
+ context "with 3 entries" do
44
+ before { subject << :one << :two << :three }
45
+
46
+ it "has latest 2 entries connected by an arrow" do
47
+ subject.to_s.should == "two -> three"
48
+ end
49
+ end
50
+
51
+ context "with more than 3 entries" do
52
+ before { Range.new(1, 10).each {|i| subject << i } }
53
+
54
+ it "has latest 2 entries connected by an arrow" do
55
+ subject.to_s.should == "9 -> 10"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,100 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ class MockMapper
4
+ def initialize
5
+ @instances = []
6
+ @gone_instances = []
7
+ @takeovers_received = []
8
+ @notifies_received = []
9
+ end
10
+ attr_reader :takeovers_received, :notifies_received
11
+
12
+ def token_for(instance_id)
13
+ if @instances.include?(instance_id)
14
+ "token-for-#{instance_id}"
15
+ else
16
+ "unknown-token"
17
+ end
18
+ end
19
+
20
+ def takeover(instance_id)
21
+ unless @gone_instances.include?(instance_id)
22
+ @takeovers_received << instance_id
23
+ true
24
+ else
25
+ false
26
+ end
27
+ end
28
+
29
+ def notify(instance_id)
30
+ @notifies_received << instance_id
31
+ end
32
+
33
+ def add_instance(instance_id)
34
+ @instances << instance_id
35
+ end
36
+
37
+ def make_instance_gone(instance_id)
38
+ @gone_instances << instance_id
39
+ end
40
+ end
41
+
42
+ describe EY::Stonith::Rackapp do
43
+ before do
44
+ @mapper = MockMapper.new
45
+ EY::Stonith.callback_module = @mapper
46
+
47
+ @client = Rack::Client.new { run EY::Stonith::Rackapp }
48
+ end
49
+
50
+ context "takeover requests" do
51
+ it "returns 200 when the takeover is accepted" do
52
+ @mapper.add_instance("my-instance")
53
+
54
+ response = @client.post "/takeover", {"label" => "my-instance", "token" => "token-for-my-instance"}
55
+
56
+ response.should be_successful
57
+ response.body.to_s.should == "{}"
58
+ @mapper.takeovers_received.should == ["my-instance"]
59
+ end
60
+
61
+ it "returns 410 when the instance is no longer around" do
62
+ @mapper.add_instance("my-instance")
63
+ @mapper.make_instance_gone("my-instance")
64
+ response = @client.post "/takeover", {"label" => "my-instance", "token" => "token-for-my-instance"}
65
+
66
+ response.status.should == 410
67
+ @mapper.takeovers_received.should be_empty
68
+ end
69
+
70
+ it "returns 410 when the token is invalid" do
71
+ @mapper.add_instance("my-instance")
72
+
73
+ response = @client.post "/takeover", {"label" => "my-instance", "token" => "invalid-token"}
74
+
75
+ response.status.should == 410
76
+ @mapper.takeovers_received.should be_empty
77
+ end
78
+ end
79
+
80
+ context "notify requests" do
81
+ it "returns 200 when the notify is accepted" do
82
+ @mapper.add_instance("my-instance")
83
+
84
+ response = @client.post "/notify", {"label" => "my-instance", "token" => "token-for-my-instance"}
85
+
86
+ response.should be_successful
87
+ response.body.to_s.should == "{}"
88
+ @mapper.notifies_received.should == ["my-instance"]
89
+ end
90
+
91
+ it "returns 403 when the token is invalid" do
92
+ @mapper.add_instance("my-instance")
93
+
94
+ response = @client.post "/notify", {"label" => "my-instance", "token" => "invalid-token"}
95
+
96
+ response.status.should == 403
97
+ @mapper.notifies_received.should be_empty
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,24 @@
1
+ unless defined? Bundler
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ Bundler.setup
5
+ end
6
+
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ require File.expand_path('../../lib/ey_stonith', __FILE__)
9
+
10
+ require 'randexp'
11
+ require 'rack/client'
12
+ require 'helpers'
13
+
14
+ Randexp::Dictionary.words
15
+ Thread.abort_on_exception = true
16
+
17
+ Spec::Runner.configure do |config|
18
+ config.include(Helpers)
19
+
20
+ def reset_history(path)
21
+ EY::Stonith::History.new(path).reset
22
+ end
23
+
24
+ end
metadata CHANGED
@@ -1,28 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ey_stonith
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
5
- prerelease: false
4
+ hash: 961916000
5
+ prerelease: true
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 6
10
- version: 0.3.6
8
+ - 4
9
+ - 1
10
+ - pre
11
+ version: 0.4.1.pre
11
12
  platform: ruby
12
13
  authors:
13
14
  - Ezra Zygmuntowicz
14
15
  - Larry Diehl
15
16
  - Martin Emde
17
+ - Tim Carey-Smith
18
+ - Jason Hansen
16
19
  autorequire:
17
20
  bindir: bin
18
21
  cert_chain: []
19
22
 
20
- date: 2010-08-20 00:00:00 -07:00
23
+ date: 2011-01-21 00:00:00 -08:00
21
24
  default_executable:
22
25
  dependencies:
23
26
  - !ruby/object:Gem::Dependency
27
+ name: json
24
28
  prerelease: false
25
- version_requirements: &id001 !ruby/object:Gem::Requirement
29
+ requirement: &id001 !ruby/object:Gem::Requirement
26
30
  none: false
27
31
  requirements:
28
32
  - - ">="
@@ -31,58 +35,197 @@ dependencies:
31
35
  segments:
32
36
  - 0
33
37
  version: "0"
34
- requirement: *id001
35
38
  type: :runtime
36
- name: json
39
+ version_requirements: *id001
37
40
  - !ruby/object:Gem::Dependency
41
+ name: sinatra
38
42
  prerelease: false
39
- version_requirements: &id002 !ruby/object:Gem::Requirement
43
+ requirement: &id002 !ruby/object:Gem::Requirement
40
44
  none: false
41
45
  requirements:
42
46
  - - ~>
43
47
  - !ruby/object:Gem::Version
44
- hash: 23
48
+ hash: 15
45
49
  segments:
50
+ - 1
46
51
  - 0
52
+ version: "1.0"
53
+ type: :runtime
54
+ version_requirements: *id002
55
+ - !ruby/object:Gem::Dependency
56
+ name: SystemTimer
57
+ prerelease: false
58
+ requirement: &id003 !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ hash: 11
64
+ segments:
65
+ - 1
47
66
  - 2
48
- - 0
49
- version: 0.2.0
50
- requirement: *id002
67
+ version: "1.2"
51
68
  type: :runtime
52
- name: fog
69
+ version_requirements: *id003
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec
72
+ prerelease: false
73
+ requirement: &id004 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ hash: 3
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ type: :development
83
+ version_requirements: *id004
84
+ - !ruby/object:Gem::Dependency
85
+ name: cucumber
86
+ prerelease: false
87
+ requirement: &id005 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ type: :development
97
+ version_requirements: *id005
98
+ - !ruby/object:Gem::Dependency
99
+ name: open4
100
+ prerelease: false
101
+ requirement: &id006 !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ hash: 3
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ type: :development
111
+ version_requirements: *id006
53
112
  - !ruby/object:Gem::Dependency
113
+ name: aruba
54
114
  prerelease: false
55
- version_requirements: &id003 !ruby/object:Gem::Requirement
115
+ requirement: &id007 !ruby/object:Gem::Requirement
56
116
  none: false
57
117
  requirements:
58
118
  - - "="
59
119
  - !ruby/object:Gem::Version
60
- hash: 25
120
+ hash: 23
61
121
  segments:
62
- - 1
63
122
  - 0
64
- - 7
65
- version: 1.0.7
66
- requirement: *id003
67
- type: :runtime
68
- name: redis
123
+ - 3
124
+ - 2
125
+ version: 0.3.2
126
+ type: :development
127
+ version_requirements: *id007
128
+ - !ruby/object:Gem::Dependency
129
+ name: ruby-debug
130
+ prerelease: false
131
+ requirement: &id008 !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ hash: 3
137
+ segments:
138
+ - 0
139
+ version: "0"
140
+ type: :development
141
+ version_requirements: *id008
142
+ - !ruby/object:Gem::Dependency
143
+ name: rake
144
+ prerelease: false
145
+ requirement: &id009 !ruby/object:Gem::Requirement
146
+ none: false
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ hash: 3
151
+ segments:
152
+ - 0
153
+ version: "0"
154
+ type: :development
155
+ version_requirements: *id009
156
+ - !ruby/object:Gem::Dependency
157
+ name: randexp
158
+ prerelease: false
159
+ requirement: &id010 !ruby/object:Gem::Requirement
160
+ none: false
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ hash: 3
165
+ segments:
166
+ - 0
167
+ version: "0"
168
+ type: :development
169
+ version_requirements: *id010
170
+ - !ruby/object:Gem::Dependency
171
+ name: cucumber
172
+ prerelease: false
173
+ requirement: &id011 !ruby/object:Gem::Requirement
174
+ none: false
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ hash: 3
179
+ segments:
180
+ - 0
181
+ version: "0"
182
+ type: :development
183
+ version_requirements: *id011
184
+ - !ruby/object:Gem::Dependency
185
+ name: rcov
186
+ prerelease: false
187
+ requirement: &id012 !ruby/object:Gem::Requirement
188
+ none: false
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ hash: 3
193
+ segments:
194
+ - 0
195
+ version: "0"
196
+ type: :development
197
+ version_requirements: *id012
69
198
  - !ruby/object:Gem::Dependency
199
+ name: realweb
70
200
  prerelease: false
71
- version_requirements: &id004 !ruby/object:Gem::Requirement
201
+ requirement: &id013 !ruby/object:Gem::Requirement
72
202
  none: false
73
203
  requirements:
74
204
  - - ~>
75
205
  - !ruby/object:Gem::Version
76
- hash: 55
206
+ hash: 19
77
207
  segments:
78
208
  - 0
79
- - 9
80
- - 6
81
- version: 0.9.6
82
- requirement: *id004
83
- type: :runtime
84
- name: sinatra
85
- description: Shoot The Other Node In The Head
209
+ - 1
210
+ - 4
211
+ version: 0.1.4
212
+ type: :development
213
+ version_requirements: *id013
214
+ - !ruby/object:Gem::Dependency
215
+ name: rack-client
216
+ prerelease: false
217
+ requirement: &id014 !ruby/object:Gem::Requirement
218
+ none: false
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ hash: 3
223
+ segments:
224
+ - 0
225
+ version: "0"
226
+ type: :development
227
+ version_requirements: *id014
228
+ description: Shoot The Other Node In The Head (with a nerf gun)
86
229
  email: awsmdev@engineyard.com
87
230
  executables:
88
231
  - stonith
@@ -104,9 +247,39 @@ extra_rdoc_files:
104
247
  - README.rdoc
105
248
  - LICENSE
106
249
  files:
107
- - lib/ey_stonith/address_stealer.rb
250
+ - .gitignore
251
+ - Gemfile
252
+ - LICENSE
253
+ - README.rdoc
254
+ - Rakefile
255
+ - bin/stonith
256
+ - bin/stonith-check
257
+ - bin/stonith-claim
258
+ - bin/stonith-commands
259
+ - bin/stonith-cron
260
+ - bin/stonith-help
261
+ - bin/stonith-info
262
+ - bin/stonith-notify
263
+ - bin/stonith-reset
264
+ - bin/stonith-resume
265
+ - bin/stonith-status
266
+ - bin/stonith-stop
267
+ - bin/stonith-takeover
268
+ - ey_stonith.gemspec
269
+ - features/check.feature
270
+ - features/cron.feature
271
+ - features/fixtures/dead.ru
272
+ - features/fixtures/healthy.ru
273
+ - features/help.feature
274
+ - features/not_found.feature
275
+ - features/notify.feature
276
+ - features/resume.feature
277
+ - features/steps/stonith_steps.rb
278
+ - features/stop.feature
279
+ - features/support/env.rb
280
+ - lib/ey_stonith.rb
108
281
  - lib/ey_stonith/awsm_notifier.rb
109
- - lib/ey_stonith/check_recorder.rb
282
+ - lib/ey_stonith/commands.rb
110
283
  - lib/ey_stonith/commands/abstract.rb
111
284
  - lib/ey_stonith/commands/check.rb
112
285
  - lib/ey_stonith/commands/claim.rb
@@ -121,28 +294,16 @@ files:
121
294
  - lib/ey_stonith/commands/status.rb
122
295
  - lib/ey_stonith/commands/stop.rb
123
296
  - lib/ey_stonith/commands/takeover.rb
124
- - lib/ey_stonith/commands.rb
125
297
  - lib/ey_stonith/config.rb
126
- - lib/ey_stonith/data.rb
127
- - lib/ey_stonith/database.rb
128
298
  - lib/ey_stonith/history.rb
129
299
  - lib/ey_stonith/rackapp.rb
130
- - lib/ey_stonith.rb
131
- - bin/stonith
132
- - bin/stonith-check
133
- - bin/stonith-claim
134
- - bin/stonith-commands
135
- - bin/stonith-cron
136
- - bin/stonith-help
137
- - bin/stonith-info
138
- - bin/stonith-notify
139
- - bin/stonith-reset
140
- - bin/stonith-resume
141
- - bin/stonith-status
142
- - bin/stonith-stop
143
- - bin/stonith-takeover
144
- - README.rdoc
145
- - LICENSE
300
+ - spec/config_spec.rb
301
+ - spec/fixtures/config.yml
302
+ - spec/fixtures/empty.yml
303
+ - spec/helpers.rb
304
+ - spec/history_spec.rb
305
+ - spec/rackapp_spec.rb
306
+ - spec/spec_helper.rb
146
307
  has_rdoc: true
147
308
  homepage: http://engineyard.com/cloud
148
309
  licenses: []
@@ -164,18 +325,37 @@ required_ruby_version: !ruby/object:Gem::Requirement
164
325
  required_rubygems_version: !ruby/object:Gem::Requirement
165
326
  none: false
166
327
  requirements:
167
- - - ">="
328
+ - - ">"
168
329
  - !ruby/object:Gem::Version
169
- hash: 3
330
+ hash: 25
170
331
  segments:
171
- - 0
172
- version: "0"
332
+ - 1
333
+ - 3
334
+ - 1
335
+ version: 1.3.1
173
336
  requirements: []
174
337
 
175
338
  rubyforge_project:
176
339
  rubygems_version: 1.3.7
177
340
  signing_key:
178
341
  specification_version: 3
179
- summary: Shoot The Other Node In The Head
180
- test_files: []
181
-
342
+ summary: Shoot The Other Node In The Head (with a nerf gun)
343
+ test_files:
344
+ - features/check.feature
345
+ - features/cron.feature
346
+ - features/fixtures/dead.ru
347
+ - features/fixtures/healthy.ru
348
+ - features/help.feature
349
+ - features/not_found.feature
350
+ - features/notify.feature
351
+ - features/resume.feature
352
+ - features/steps/stonith_steps.rb
353
+ - features/stop.feature
354
+ - features/support/env.rb
355
+ - spec/config_spec.rb
356
+ - spec/fixtures/config.yml
357
+ - spec/fixtures/empty.yml
358
+ - spec/helpers.rb
359
+ - spec/history_spec.rb
360
+ - spec/rackapp_spec.rb
361
+ - spec/spec_helper.rb
@@ -1,40 +0,0 @@
1
- require 'fog'
2
-
3
- module EY
4
- module Stonith
5
- class AddressStealer
6
- def self.fog(credentials) Fog::AWS::EC2.new(credentials) end
7
-
8
- def initialize(server_id, ip, credentials)
9
- @fog = self.class.fog(credentials)
10
- server = @fog.servers.get(server_id)
11
- main_address = @fog.addresses.get(ip)
12
-
13
- @address = if !main_address.server_id
14
- main_address
15
- elsif server
16
- address_for(server)
17
- end
18
- end
19
-
20
- def associate(server_id)
21
- server = @fog.servers.get(server_id)
22
- #TODO: handle error
23
- raise "Don't have any IPs to use!" unless @address
24
- raise "Already have an IP" if address_for(server)
25
- @address.server = server
26
- end
27
-
28
- def ip
29
- @address.public_ip
30
- end
31
-
32
- private
33
-
34
- def address_for(server)
35
- @fog.addresses.detect{|addr| addr.server_id == server.id }
36
- end
37
- end
38
- end
39
- end
40
-
@@ -1,55 +0,0 @@
1
- module EY
2
- module Stonith
3
- class CheckRecorder
4
- BAD_CHECK_MAX = 5
5
-
6
- attr_reader :bad
7
-
8
- def initialize(pathname)
9
- @pathname = pathname
10
- rehydrate
11
- end
12
-
13
- def bad_check!(key)
14
- reset_on_key_change(key)
15
- log_bad_check
16
- @bad += 1 if @seen_good
17
- save
18
- end
19
-
20
- def good_check!(key)
21
- @key, @bad, @seen_good = key, 0, true
22
- save
23
- end
24
-
25
- def limit_exceeded?
26
- @seen_good && @bad > BAD_CHECK_MAX
27
- end
28
-
29
- protected
30
-
31
- def save
32
- @pathname.open('w') { |f| f << Marshal.dump([@key, @bad, @seen_good]) }
33
- end
34
-
35
- def rehydrate
36
- @key, @bad, @seen_good = Marshal.load(@pathname.read) if @pathname.readable?
37
- end
38
-
39
- def reset
40
- @key, @bad, @seen_good = nil, 0, false
41
- end
42
-
43
- def reset_on_key_change(key)
44
- unless @key == key
45
- reset
46
- @key = key
47
- end
48
- end
49
-
50
- def log_bad_check
51
- Stonith.logger.warn("Bad check against #{@key}. Seen good? #{@seen_good}")
52
- end
53
- end
54
- end
55
- end
@@ -1,11 +0,0 @@
1
- module EY
2
- module Stonith
3
- class Data < Struct.new(:hostname, :instance_id, :ip)
4
- alias_method :key, :instance_id
5
-
6
- def to_s
7
- "[#{hostname.inspect}, #{instance_id.inspect}, #{ip.inspect}]"
8
- end
9
- end
10
- end
11
- end