unravel 0.1.1 → 0.2.0
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/README.md +2 -0
- data/lib/unravel.rb +82 -24
- data/lib/unravel/exec.rb +37 -6
- data/lib/unravel/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45a416271c45d03fd6342e3296481e60d966df23
|
4
|
+
data.tar.gz: 5268e00db454da494c02ca35dcadd1bccbd27371
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dca889ad5427a5173cbbe2545a2b997c89b25a63c2c2ef4eaee2c3da4a347e6a78fcfebc746a2c954094aa52dd12d4fcc10750839b52dc28e704e879a47a8731
|
7
|
+
data.tar.gz: 8b0b5d637a46caafa23e478218355460ab08db89c1c823b69c90718ee14b88a305512d8868d3857af29f69a912ef488168ce3e71ed35493f2222d24ef42b5211
|
data/README.md
CHANGED
data/lib/unravel.rb
CHANGED
@@ -21,6 +21,19 @@ module Unravel
|
|
21
21
|
class NoKnownRootCause < HumanInterventionNeeded ; end
|
22
22
|
class SameCauseReoccurringCause < HumanInterventionNeeded; end
|
23
23
|
|
24
|
+
class NoErrorHandler
|
25
|
+
attr_reader :name
|
26
|
+
attr_reader :exception
|
27
|
+
def initialize(name, ex)
|
28
|
+
@name = name
|
29
|
+
@exception = ex
|
30
|
+
end
|
31
|
+
|
32
|
+
def message
|
33
|
+
"Achievement #{name.inspect} is not declared to handle exceptions of type: #{ex.class}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
24
37
|
class Registry
|
25
38
|
attr_reader :achievements, :symptoms, :fixes, :contexts, :errors
|
26
39
|
|
@@ -32,14 +45,18 @@ module Unravel
|
|
32
45
|
@errors = {}
|
33
46
|
end
|
34
47
|
|
35
|
-
def
|
36
|
-
@fixes
|
37
|
-
|
38
|
-
|
48
|
+
def has_fix_for?(cause)
|
49
|
+
@fixes.key?(cause)
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_fix_for(cause)
|
53
|
+
achievement_or_block = @fixes[cause]
|
54
|
+
fail HumanInterventionNeeded, "No fix for: #{cause}" unless achievement_or_block
|
55
|
+
achievement_or_block
|
39
56
|
end
|
40
57
|
|
41
58
|
def add_fix(name, block)
|
42
|
-
fail HumanInterventionNeeded, "fix
|
59
|
+
fail HumanInterventionNeeded, "fix for root cause #{name} already exists" if @fixes.key?(name)
|
43
60
|
@fixes[name] = block
|
44
61
|
end
|
45
62
|
|
@@ -77,39 +94,62 @@ module Unravel
|
|
77
94
|
end
|
78
95
|
|
79
96
|
class Session
|
97
|
+
class DefaultConfig
|
98
|
+
def max_retries
|
99
|
+
5
|
100
|
+
end
|
101
|
+
|
102
|
+
def after_achievement_success(name)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
80
106
|
attr_reader :registry
|
107
|
+
attr_reader :config
|
81
108
|
|
82
109
|
class FixableError < RuntimeError
|
83
110
|
attr_reader :symptom
|
84
|
-
|
111
|
+
attr_reader :extracted_info
|
112
|
+
def initialize(symptom_name, extracted_info)
|
85
113
|
@symptom = symptom_name
|
114
|
+
|
115
|
+
# TODO: clean this up
|
116
|
+
matchdata = extracted_info
|
117
|
+
@extracted_info = matchdata.captures.empty? ? [] : [matchdata]
|
86
118
|
end
|
87
119
|
end
|
88
120
|
|
89
|
-
def initialize
|
121
|
+
def initialize(config = DefaultConfig.new)
|
122
|
+
@config = config
|
90
123
|
@registry = Registry.new
|
91
124
|
end
|
92
125
|
|
93
|
-
def achieve(name,
|
126
|
+
def achieve(name, *args)
|
94
127
|
check # TODO: overhead?
|
95
128
|
|
96
129
|
prev_causes = Set.new
|
130
|
+
max_retries = config.max_retries
|
97
131
|
retries_left = max_retries
|
98
132
|
|
99
133
|
begin
|
100
134
|
Unravel.logger.info("Achieving (attempt: #{max_retries - retries_left + 1}/#{max_retries}): #{name.inspect}")
|
101
135
|
|
102
136
|
block = registry.get_achievement(name)
|
137
|
+
if block.arity >= 0
|
138
|
+
unless block.arity == args.size
|
139
|
+
fail ArgumentError, "expected #{block.arity} args for #{name.inspect}, got: #{args.inspect}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
103
143
|
error_contexts = registry.error_contexts_for_achievement(name)
|
104
144
|
|
105
145
|
begin
|
106
|
-
res = return_wrap(&block)
|
146
|
+
res = return_wrap(*args, &block)
|
107
147
|
rescue *error_contexts.keys
|
108
148
|
ex = $!
|
109
149
|
econtext = error_contexts[ex.class]
|
110
150
|
unless econtext
|
111
151
|
# TODO: not tested
|
112
|
-
fail
|
152
|
+
fail NoErrorHandler.new(name, ex)
|
113
153
|
end
|
114
154
|
|
115
155
|
econtext.each do |fix_name|
|
@@ -119,7 +159,11 @@ module Unravel
|
|
119
159
|
fail
|
120
160
|
end
|
121
161
|
|
122
|
-
|
162
|
+
if res == true
|
163
|
+
config.after_achievement_success(name)
|
164
|
+
return true
|
165
|
+
end
|
166
|
+
|
123
167
|
fail NotImplementedError, "#{name} unexpectedly returned #{res.inspect} (expected true or exception)"
|
124
168
|
|
125
169
|
rescue FixableError => error
|
@@ -131,12 +175,18 @@ module Unravel
|
|
131
175
|
fail NoKnownRootCause, "Can't find root cause for: #{error.symptom}, #{error.message}" unless cause
|
132
176
|
|
133
177
|
Unravel.logger.info("#{name}: Cause: #{cause.inspect}")
|
134
|
-
|
135
|
-
|
178
|
+
|
179
|
+
# Since causes can be generic (parametized), they're unique based on error matchers
|
180
|
+
unique_context = error.extracted_info
|
181
|
+
unique_cause = [cause, unique_context]
|
182
|
+
|
183
|
+
if prev_causes.include? unique_cause
|
184
|
+
fail SameCauseReoccurringCause, "#{cause.to_s} (with #{unique_context.inspect}) wasn't ultimately fixed (it occured again)"
|
136
185
|
end
|
137
186
|
|
138
|
-
prev_causes <<
|
139
|
-
|
187
|
+
prev_causes << unique_cause
|
188
|
+
fix = registry.get_fix_for(cause)
|
189
|
+
fix.call(error)
|
140
190
|
|
141
191
|
retries_left -= 1
|
142
192
|
retry if retries_left > 0
|
@@ -170,7 +220,13 @@ module Unravel
|
|
170
220
|
if name.is_a?(Hash)
|
171
221
|
name, achievement = *name.first
|
172
222
|
end
|
173
|
-
|
223
|
+
# TODO: this recursively calls self (just to provied block), though -
|
224
|
+
# is the block_given check needed?
|
225
|
+
unless block_given?
|
226
|
+
fix_for(name) do |error|
|
227
|
+
achieve(achievement, *error.extracted_info)
|
228
|
+
end
|
229
|
+
end
|
174
230
|
end
|
175
231
|
end
|
176
232
|
|
@@ -179,14 +235,18 @@ module Unravel
|
|
179
235
|
root_cause_name = "no_#{fix_name}".to_sym
|
180
236
|
error[error_name] = regexp
|
181
237
|
root_cause_for error_name => root_cause_name
|
182
|
-
|
238
|
+
unless registry.has_fix_for?(root_cause_name)
|
239
|
+
fix_for root_cause_name => fix_name
|
240
|
+
end
|
183
241
|
achievement fix_name, handlers, &method(fix_name)
|
184
242
|
end
|
185
243
|
|
186
244
|
private
|
187
245
|
|
188
|
-
def return_wrap(&block)
|
189
|
-
Thread.new
|
246
|
+
def return_wrap(*args, &block)
|
247
|
+
Thread.new do
|
248
|
+
return block.yield(*args)
|
249
|
+
end.join
|
190
250
|
rescue LocalJumpError => ex
|
191
251
|
ex.exit_value
|
192
252
|
end
|
@@ -213,16 +273,14 @@ module Unravel
|
|
213
273
|
unless regexp
|
214
274
|
fail HumanInterventionNeeded, "Unregistered error: #{name} to match #{error.inspect}"
|
215
275
|
end
|
276
|
+
|
216
277
|
# TODO: encoding not tested
|
217
|
-
|
278
|
+
match = regexp.match(error.force_encoding(Encoding::ASCII_8BIT))
|
279
|
+
fail FixableError.new(name, match) if match
|
218
280
|
end
|
219
281
|
|
220
282
|
def get_root_cause_for(symptom)
|
221
283
|
registry.get_root_cause(symptom)
|
222
284
|
end
|
223
|
-
|
224
|
-
def recipe_for(name, &block)
|
225
|
-
registry.get_fix(name)
|
226
|
-
end
|
227
285
|
end
|
228
286
|
end
|
data/lib/unravel/exec.rb
CHANGED
@@ -1,6 +1,35 @@
|
|
1
1
|
module Unravel
|
2
2
|
class Exec
|
3
|
-
class Error < Unravel::HumanInterventionNeeded
|
3
|
+
class Error < Unravel::HumanInterventionNeeded
|
4
|
+
class Standard < Error
|
5
|
+
end
|
6
|
+
|
7
|
+
class Silent < Error
|
8
|
+
attr_reader :exitcode
|
9
|
+
attr_reader :exitstatus
|
10
|
+
def initialize(exitcode, stdout)
|
11
|
+
@exitcode = exitcode
|
12
|
+
@stdout = stdout
|
13
|
+
super()
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
message
|
18
|
+
end
|
19
|
+
|
20
|
+
def message
|
21
|
+
lines = @stdout.lines.to_a
|
22
|
+
output =
|
23
|
+
if lines.size > 1
|
24
|
+
indent = "\n stdout"
|
25
|
+
"#{indent}: #{lines * indent}\n"
|
26
|
+
else
|
27
|
+
"stdout: #{@stdout.inspect}"
|
28
|
+
end
|
29
|
+
"No stderr available: #{output} (exited with #{@exitcode})"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
4
33
|
end
|
5
34
|
|
6
35
|
class << self
|
@@ -16,10 +45,12 @@ module Unravel
|
|
16
45
|
return true if status.success?
|
17
46
|
Unravel.logger.debug "Errors from #{args.inspect}: -----"
|
18
47
|
Unravel.logger.debug "#{error}"
|
19
|
-
|
20
|
-
|
48
|
+
|
49
|
+
# TODO: is strip a good idea?
|
50
|
+
raise Exec::Error::Silent.new(status.exitstatus, out) if error.strip.empty?
|
51
|
+
raise Exec::Error::Standard, error
|
21
52
|
rescue Errno::ENOENT => e
|
22
|
-
raise Exec::Error, e.message
|
53
|
+
raise Exec::Error::ENOENT, e.message
|
23
54
|
end
|
24
55
|
|
25
56
|
def Capture(args)
|
@@ -29,9 +60,9 @@ module Unravel
|
|
29
60
|
Unravel.logger.debug "Errors from #{args.inspect}: -----"
|
30
61
|
Unravel.logger.debug "#{error}"
|
31
62
|
error = out if error.strip.empty?
|
32
|
-
raise Exec::Error, error
|
63
|
+
raise Exec::Error::Standard, error
|
33
64
|
rescue Errno::ENOENT => e
|
34
|
-
raise Exec::Error, e.message
|
65
|
+
raise Exec::Error::ENOENT, e.message
|
35
66
|
end
|
36
67
|
end
|
37
68
|
end
|
data/lib/unravel/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unravel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cezary Baginski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Tool for solving non-deterministic problems given goals, symptoms, fixes
|
14
14
|
and root causes
|