ey_stonith 0.3.6 → 0.4.1.pre

Sign up to get free protection for your applications and to get access to all the features.
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