marty 1.1.5 → 1.1.6
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/controllers/marty/diagnostic_controller.rb +15 -416
- data/app/models/diagnostic/aws/ec2_instance.rb +100 -0
- data/app/models/diagnostic/base.rb +69 -0
- data/app/models/diagnostic/base_collection.rb +10 -0
- data/app/models/diagnostic/collection.rb +10 -0
- data/app/models/diagnostic/delayed_job.rb +48 -0
- data/app/models/diagnostic/env.rb +35 -0
- data/app/models/diagnostic/environment.rb +35 -0
- data/app/models/diagnostic/fatal.rb +12 -0
- data/app/models/diagnostic/helper.rb +11 -0
- data/app/models/diagnostic/nodes.rb +18 -0
- data/app/models/diagnostic/reporter.rb +108 -0
- data/app/models/diagnostic/request.rb +28 -0
- data/app/models/diagnostic/version.rb +17 -0
- data/app/models/marty/helper.rb +0 -8
- data/app/views/marty/diagnostic/diag.html.erb +15 -19
- data/app/views/marty/diagnostic/op.html.erb +18 -19
- data/delorean/diagnostics.dl +1 -1
- data/lib/diagnostic/database.rb +28 -0
- data/lib/diagnostic/node.rb +35 -0
- data/lib/diagnostic/packer.rb +47 -0
- data/lib/marty/version.rb +1 -1
- data/spec/controllers/diagnostic_controller_spec.rb +18 -175
- data/spec/models/diagnostic/base_spec.rb +98 -0
- data/spec/models/diagnostic/collection_spec.rb +32 -0
- data/spec/models/diagnostic/delayed_job_spec.rb +46 -0
- data/spec/models/diagnostic/reporter_spec.rb +319 -0
- metadata +21 -1
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Diagnostic::Base do
|
4
|
+
def sample_data consistent=true
|
5
|
+
node_data_a = Diagnostic::Base.pack(include_ip=false){'A'}
|
6
|
+
node_data_b = Diagnostic::Base.pack(include_ip=false){'B'}
|
7
|
+
|
8
|
+
data = {
|
9
|
+
'NodeA' => node_data_a,
|
10
|
+
'NodeB' => node_data_a,
|
11
|
+
}
|
12
|
+
|
13
|
+
return data if consistent
|
14
|
+
data + {'NodeB' => node_data_b}
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'determines consistency of aggregate diagnostics' do
|
18
|
+
a = sample_data
|
19
|
+
b = sample_data(consistent=false)
|
20
|
+
|
21
|
+
expect(Diagnostic::Base.consistent?(a)).to eq(true)
|
22
|
+
expect(Diagnostic::Base.consistent?(b)).to eq(false)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'can produce a valid diagnostic hash from a String' do
|
26
|
+
expected = {
|
27
|
+
'Base' => {
|
28
|
+
'description' => 'A',
|
29
|
+
'status' => true,
|
30
|
+
'consistent' => nil
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
expect(Diagnostic::Base.pack(include_ip=false){'A'}).to eq(expected)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'can produce a valid diagnostic hash from a Hash' do
|
38
|
+
test_a = {
|
39
|
+
'ImportantA' => 'A',
|
40
|
+
'ImportantB' => 'B',
|
41
|
+
'ImportantC' => 'C'}
|
42
|
+
|
43
|
+
test_b = {
|
44
|
+
'ImportantA' => {
|
45
|
+
'description' => 'A', 'status' => true, 'consistent' => nil},
|
46
|
+
'ImportantB' => 'B',
|
47
|
+
'ImportantC' => 'C'}
|
48
|
+
|
49
|
+
expected = {
|
50
|
+
'ImportantA' => {
|
51
|
+
'description' => 'A', 'status' => true, 'consistent' => nil
|
52
|
+
},
|
53
|
+
'ImportantB' => {
|
54
|
+
'description' => 'B', 'status' => true, 'consistent' => nil
|
55
|
+
},
|
56
|
+
'ImportantC' => {
|
57
|
+
'description' => 'C', 'status' => true, 'consistent' => nil
|
58
|
+
},
|
59
|
+
}
|
60
|
+
|
61
|
+
expect(Diagnostic::Base.pack(include_ip=false){test_a}).to eq(expected)
|
62
|
+
expect(Diagnostic::Base.pack(include_ip=false){test_a}).to eq(expected)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'can produce a valid diagnostic hash from an error Hash' do
|
66
|
+
test = Diagnostic::Base.pack(include_ip=false){
|
67
|
+
Diagnostic::Base.error('E')
|
68
|
+
}
|
69
|
+
|
70
|
+
expected = {
|
71
|
+
"Base"=>{
|
72
|
+
"description"=>"E",
|
73
|
+
"status"=>false,
|
74
|
+
"consistent"=>nil}
|
75
|
+
}
|
76
|
+
|
77
|
+
expect(test).to eq(expected)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'will raise an error if Hash is invalid.' do
|
81
|
+
test_a = {
|
82
|
+
'ImportantA' => 'A',
|
83
|
+
'ImportantB' => 'B',
|
84
|
+
'ImportantC' => Diagnostic::Base.create_info('C') + {'extra' => 'D'}
|
85
|
+
}
|
86
|
+
|
87
|
+
test_b = {
|
88
|
+
'Test' => {
|
89
|
+
'ImportantA' => 'A',
|
90
|
+
'ImportantB' => 'B',
|
91
|
+
'ImportantC' => 'C',
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
expect{Diagnostic::Base.pack{test_a}}.to raise_error(RuntimeError)
|
96
|
+
expect{Diagnostic::Base.pack{test_b}}.to raise_error(RuntimeError)
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Diagnostic::Collection do
|
4
|
+
def sample_data consistent = true
|
5
|
+
node_a_data = Diagnostic::Collection.pack(include_ip=false){'A'}
|
6
|
+
data = {
|
7
|
+
'NodeA' => node_a_data,
|
8
|
+
'NodeB' => node_a_data,
|
9
|
+
}
|
10
|
+
return data if consistent
|
11
|
+
data + {'NodeB' => {'Base' => Diagnostic::Collection.error('B')}}
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'all diagnostics in diagnostics class attribute are generated' do
|
15
|
+
diags = [Diagnostic::Version, Diagnostic::Environment]
|
16
|
+
expected = diags.map{|d| d.generate}.reduce(:deep_merge)
|
17
|
+
Diagnostic::Collection.diagnostics = diags
|
18
|
+
expect(Diagnostic::Collection.generate).to eq(expected)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'declares data consistency via status consistency' do
|
22
|
+
a = sample_data
|
23
|
+
b = sample_data + {
|
24
|
+
'NodeB' => Diagnostic::Collection.pack(include_ip=false){'B'}
|
25
|
+
}
|
26
|
+
c = sample_data(consistent=false)
|
27
|
+
|
28
|
+
expect(Diagnostic::Collection.consistent?(a)).to eq(true)
|
29
|
+
expect(Diagnostic::Collection.consistent?(b)).to eq(true)
|
30
|
+
expect(Diagnostic::Collection.consistent?(c)).to eq(false)
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'job_helper'
|
3
|
+
|
4
|
+
describe Diagnostic::DelayedJob do
|
5
|
+
# used to stub request object
|
6
|
+
class DummyRequest
|
7
|
+
attr_accessor :params, :port
|
8
|
+
def initialize
|
9
|
+
@params = {}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
before(:all) do
|
14
|
+
Marty::Script.load_scripts(nil, Date.today)
|
15
|
+
end
|
16
|
+
|
17
|
+
before(:each) do
|
18
|
+
allow(Diagnostic::DelayedJob).to receive(:scope).and_return(nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
def sample_data
|
22
|
+
{
|
23
|
+
Diagnostic::Helper.my_ip => {
|
24
|
+
'Version' => {
|
25
|
+
'description' => Marty::VERSION,
|
26
|
+
'status' => true,
|
27
|
+
'consistent' => nil
|
28
|
+
},
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'can detect if all workers are running correct application version' do
|
34
|
+
ENV['DELAYED_VER'] = Marty::VERSION
|
35
|
+
start_delayed_job
|
36
|
+
expect(Diagnostic::DelayedJob.generate).to eq(sample_data)
|
37
|
+
stop_delayed_job
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'will fail if DELAYED_VER is not set' do
|
41
|
+
ENV.delete('DELAYED_VER')
|
42
|
+
start_delayed_job
|
43
|
+
expect{Diagnostic::DelayedJob.generate}.to raise_error(RuntimeError)
|
44
|
+
stop_delayed_job
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,319 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'job_helper'
|
3
|
+
|
4
|
+
describe Diagnostic::Reporter do
|
5
|
+
# used to stub request object
|
6
|
+
class DummyRequest
|
7
|
+
attr_accessor :params, :port
|
8
|
+
end
|
9
|
+
|
10
|
+
def params diagnostic='base', scope=nil
|
11
|
+
{op: diagnostic, scope: scope}
|
12
|
+
end
|
13
|
+
|
14
|
+
def git
|
15
|
+
begin
|
16
|
+
message = `cd #{Rails.root.to_s}; git describe --tags --always;`.strip
|
17
|
+
rescue
|
18
|
+
message = error("Failed accessing git")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def aggregate_data opts={}
|
23
|
+
{
|
24
|
+
'Diagnostic::Dummy' => {
|
25
|
+
'NodeA' => {
|
26
|
+
'ImportantTest' => {
|
27
|
+
'description' => 'A',
|
28
|
+
'status' => opts[:status].nil? ? true : opts[:status],
|
29
|
+
'consistent' => opts[:consistent].nil? ? true : opts[:consistent],
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def aggregate_consistency_data diagnostic='Base'
|
37
|
+
original_a = Diagnostic::Base.create_info('A')
|
38
|
+
original_b = Diagnostic::Base.create_info('B')
|
39
|
+
|
40
|
+
data = {
|
41
|
+
'CONSTANTA' => original_a,
|
42
|
+
'CONSTANTB' => original_b,
|
43
|
+
'CONSTANTB2' => original_b,
|
44
|
+
}
|
45
|
+
|
46
|
+
different_b = Diagnostic::Base.create_info('C')
|
47
|
+
|
48
|
+
key = "Diagnostic::" + diagnostic
|
49
|
+
test = {
|
50
|
+
key => {
|
51
|
+
'NodeA' => data,
|
52
|
+
'NodeB' => data + {
|
53
|
+
'CONSTANTB' => different_b,
|
54
|
+
'CONSTANTB2' => different_b
|
55
|
+
},
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
inconsistent_b = Diagnostic::Base.create_info('B', true, false)
|
60
|
+
inconsistent_c = Diagnostic::Base.create_info('C', true, false)
|
61
|
+
|
62
|
+
if diagnostic == 'Env'
|
63
|
+
expected = {
|
64
|
+
key => {
|
65
|
+
'NodeA' => {
|
66
|
+
'CONSTANTB' => inconsistent_b,
|
67
|
+
'CONSTANTB2' => inconsistent_b,
|
68
|
+
},
|
69
|
+
'NodeB' => {
|
70
|
+
'CONSTANTB' => inconsistent_c,
|
71
|
+
'CONSTANTB2' => inconsistent_c,
|
72
|
+
},
|
73
|
+
}
|
74
|
+
}
|
75
|
+
else
|
76
|
+
expected = {
|
77
|
+
key => {
|
78
|
+
'NodeA' => {
|
79
|
+
'CONSTANTA' => original_a + {'consistent' => true},
|
80
|
+
'CONSTANTB' => inconsistent_b,
|
81
|
+
'CONSTANTB2' => inconsistent_b,
|
82
|
+
},
|
83
|
+
'NodeB' => {
|
84
|
+
'CONSTANTA' => original_a + {'consistent' => true},
|
85
|
+
'CONSTANTB' => inconsistent_c,
|
86
|
+
'CONSTANTB2' => inconsistent_c,
|
87
|
+
},
|
88
|
+
}
|
89
|
+
}
|
90
|
+
end
|
91
|
+
[test, expected]
|
92
|
+
end
|
93
|
+
|
94
|
+
def info v, status, consistent
|
95
|
+
Diagnostic::Base.create_info(v, status, consistent)
|
96
|
+
end
|
97
|
+
|
98
|
+
def version_data consistent = true
|
99
|
+
Diagnostic::Base.pack(include_ip=false){
|
100
|
+
{
|
101
|
+
"Marty" => info(Marty::VERSION, true, consistent),
|
102
|
+
"Delorean" => info(Delorean::VERSION, true, true),
|
103
|
+
"Mcfly" => info(Mcfly::VERSION, true, true),
|
104
|
+
"Git" => info(git, true, true),
|
105
|
+
}
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
def minimize(str)
|
110
|
+
str.gsub(/\s+/, "")
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'display mechanism for version diagnostic' do
|
114
|
+
before(:all) do
|
115
|
+
Diagnostic::Reporter.diagnostics = [Diagnostic::Version]
|
116
|
+
end
|
117
|
+
|
118
|
+
before(:each) do
|
119
|
+
Diagnostic::Reporter.request = DummyRequest.new
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'masks consistent nodes for display (version)' do
|
123
|
+
Diagnostic::Reporter.request.params = params(scope='local')
|
124
|
+
data = {
|
125
|
+
'Diagnostic::Version' => {
|
126
|
+
'NodeA' => version_data,
|
127
|
+
'NodeB' => version_data,
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
expected = <<-ERB
|
132
|
+
<h3>Version</h3>
|
133
|
+
<div class="wrapper">
|
134
|
+
<table>
|
135
|
+
<tr>
|
136
|
+
<th colspan="2" scope="col">consistent</th>
|
137
|
+
</tr>
|
138
|
+
<tr>
|
139
|
+
<th class="data" scope="row">Marty</th>
|
140
|
+
<td class="overflow passed"><p>#{Marty::VERSION}</p>
|
141
|
+
</td>
|
142
|
+
</tr>
|
143
|
+
<tr>
|
144
|
+
<th class="data" scope="row">Delorean</th>
|
145
|
+
<td class="overflow passed"><p>#{Delorean::VERSION}</p>
|
146
|
+
</td>
|
147
|
+
</tr>
|
148
|
+
<tr>
|
149
|
+
<th class="data" scope="row">Mcfly</th>
|
150
|
+
<td class="overflow passed"><p>#{Mcfly::VERSION}</p>
|
151
|
+
</td>
|
152
|
+
</tr>
|
153
|
+
<tr>
|
154
|
+
<th class="data" scope="row">Git</th>
|
155
|
+
<td class="overflow passed"><p>#{git}</p>
|
156
|
+
</td>
|
157
|
+
</tr>
|
158
|
+
</table>
|
159
|
+
</div>
|
160
|
+
ERB
|
161
|
+
|
162
|
+
test = Diagnostic::Reporter.displays(data)
|
163
|
+
expect(minimize(test)).to eq(minimize(expected))
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'displays all nodes when there is an inconsistent node (version)' do
|
167
|
+
Diagnostic::Reporter.request.params = params
|
168
|
+
bad_ver = '0.0.0'
|
169
|
+
|
170
|
+
data = {
|
171
|
+
'Diagnostic::Version' => {
|
172
|
+
'NodeA' => version_data(consistent=false),
|
173
|
+
'NodeB' => version_data + {
|
174
|
+
'Marty' => Diagnostic::Base.create_info(bad_ver, true, false)
|
175
|
+
},
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
expected = <<-ERB
|
180
|
+
<h3>Version</h3>
|
181
|
+
<h3 class="error">Inconsistency Detected </h3>
|
182
|
+
<div class="wrapper">
|
183
|
+
<table>
|
184
|
+
<tr>
|
185
|
+
<th></th>
|
186
|
+
<th scope="col">NodeA</th>
|
187
|
+
<th scope="col">NodeB</th>
|
188
|
+
</tr>
|
189
|
+
<tr>
|
190
|
+
<th class="data" scope="row">Marty</th>
|
191
|
+
<td class="overflow inconsistent"><p>#{Marty::VERSION}</p></td>
|
192
|
+
<td class="overflow inconsistent"><p>#{bad_ver}</p></td>
|
193
|
+
</tr>
|
194
|
+
<tr>
|
195
|
+
<th class="data" scope="row">Delorean</th>
|
196
|
+
<td class="overflow passed"><p>#{Delorean::VERSION}</p></td>
|
197
|
+
<td class="overflow passed"><p>#{Delorean::VERSION}</p></td>
|
198
|
+
</tr>
|
199
|
+
<tr>
|
200
|
+
<th class="data" scope="row">Mcfly</th>
|
201
|
+
<td class="overflow passed"><p>#{Mcfly::VERSION}</p></td>
|
202
|
+
<td class="overflow passed"><p>#{Mcfly::VERSION}</p></td>
|
203
|
+
</tr>
|
204
|
+
<tr>
|
205
|
+
<th class="data" scope="row">Git</th>
|
206
|
+
<td class="overflow passed"><p>#{git}</p></td>
|
207
|
+
<td class="overflow passed"><p>#{git}</p></td>
|
208
|
+
</tr>
|
209
|
+
</table>
|
210
|
+
</div>
|
211
|
+
ERB
|
212
|
+
|
213
|
+
test = Diagnostic::Reporter.displays(data)
|
214
|
+
expect(minimize(test)).to eq(minimize(expected))
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'can detect errors in diagnostic for display and api' do
|
218
|
+
Diagnostic::Reporter.request.params = params
|
219
|
+
n = aggregate_data
|
220
|
+
e = aggregate_data(status: false)
|
221
|
+
c = aggregate_data(consistent: false)
|
222
|
+
ce = aggregate_data(status: false, consistent: false)
|
223
|
+
|
224
|
+
aggregate_failures do
|
225
|
+
expect(Diagnostic::Reporter.errors(n)).to eq({})
|
226
|
+
expect(Diagnostic::Reporter.errors(e)).not_to eq({})
|
227
|
+
expect(Diagnostic::Reporter.errors(c)).not_to eq({})
|
228
|
+
expect(Diagnostic::Reporter.errors(ce)).not_to eq({})
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'can survive and display fatal errors' do
|
233
|
+
Diagnostic::Reporter.request.params = params
|
234
|
+
|
235
|
+
a_err_a = Diagnostic::Fatal.message('A',
|
236
|
+
node: 'NodeA')
|
237
|
+
|
238
|
+
a_err_b = Diagnostic::Fatal.message('B',
|
239
|
+
node: 'NodeA')
|
240
|
+
|
241
|
+
b_err_c = Diagnostic::Fatal.message('C',
|
242
|
+
node: 'NodeB',
|
243
|
+
type: 'OtherError')
|
244
|
+
|
245
|
+
c_err_d = Diagnostic::Fatal.message('D',
|
246
|
+
node: 'NodeC',
|
247
|
+
type: 'OtherOtherError')
|
248
|
+
|
249
|
+
data = [a_err_a, a_err_b, b_err_c, c_err_d].reduce(:deep_merge)
|
250
|
+
|
251
|
+
expected = <<-ERB
|
252
|
+
<h3>Fatal</h3>
|
253
|
+
<h3 class="error">Inconsistency Detected</h3>
|
254
|
+
<div class="wrapper">
|
255
|
+
<table>
|
256
|
+
<tr>
|
257
|
+
<th></th>
|
258
|
+
<th scope="col">NodeA</th>
|
259
|
+
<th scope="col">NodeB</th>
|
260
|
+
<th scope="col">NodeC</th>
|
261
|
+
</tr>
|
262
|
+
<tr><th class="data" scope="row">RuntimeError</th>
|
263
|
+
<td class="overflow error">
|
264
|
+
<p>B</p>
|
265
|
+
</td>
|
266
|
+
<td class="overflow inconsistent">
|
267
|
+
<p>N/A</p>
|
268
|
+
</td>
|
269
|
+
<td class="overflow inconsistent">
|
270
|
+
<p>N/A</p>
|
271
|
+
</td>
|
272
|
+
</tr>
|
273
|
+
<tr><th class="data" scope="row">OtherError</th>
|
274
|
+
<td class="overflow inconsistent">
|
275
|
+
<p>N/A</p>
|
276
|
+
</td>
|
277
|
+
<td class="overflow error">
|
278
|
+
<p>C</p>
|
279
|
+
</td>
|
280
|
+
<td class="overflow inconsistent">
|
281
|
+
<p>N/A</p>
|
282
|
+
</td>
|
283
|
+
</tr>
|
284
|
+
<tr><th class="data" scope="row">OtherOtherError</th>
|
285
|
+
<td class="overflow inconsistent">
|
286
|
+
<p>N/A</p>
|
287
|
+
</td>
|
288
|
+
<td class="overflow inconsistent">
|
289
|
+
<p>N/A</p>
|
290
|
+
</td>
|
291
|
+
<td class="overflow error">
|
292
|
+
<p>D</p>
|
293
|
+
</td>
|
294
|
+
</tr>
|
295
|
+
</table>
|
296
|
+
</div>
|
297
|
+
<h3 class="error">
|
298
|
+
Something went wrong.</br>
|
299
|
+
Consistency is checked between remaining nodes if applicable.
|
300
|
+
</h3>
|
301
|
+
ERB
|
302
|
+
|
303
|
+
result = Diagnostic::Reporter.displays(data)
|
304
|
+
expect(minimize(result)).to eq(minimize(expected))
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
describe 'aggregation consistency functionality' do
|
309
|
+
it 'env diagnostic' do
|
310
|
+
test, expected = aggregate_consistency_data('Env')
|
311
|
+
expect(Diagnostic::Reporter.consistency(test)).to eq(expected)
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'marks data as consistent/inconsistent' do
|
315
|
+
test, expected = aggregate_consistency_data
|
316
|
+
expect(Diagnostic::Reporter.consistency(test)).to eq(expected)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|