-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcore.go
More file actions
379 lines (331 loc) · 11.1 KB
/
Copy pathcore.go
File metadata and controls
379 lines (331 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// Package chaoskit provides a modular framework for chaos engineering.
//
// ChaosKit enables systematic testing of system reliability through
// controlled fault injection and invariant validation.
//
// # Basic Usage
//
// scenario := chaoskit.NewScenario("test").
// WithTarget(mySystem).
// Inject("delay", injectors.RandomDelay(5*time.Millisecond, 25*time.Millisecond)).
// Assert("goroutines", validators.GoroutineLimit(100)).
// Build()
//
// executor := chaoskit.NewExecutor()
// if err := executor.Run(ctx, scenario); err != nil {
// log.Fatal(err)
// }
//
// # Architecture
//
// ChaosKit follows clean architecture principles with clear separation between:
// - Scenarios: Define what to test
// - Injectors: Introduce faults into the system
// - Validators: Verify system invariants
// - Executor: Orchestrates scenario execution
//
// # Extension Points
//
// Implement the Injector or Validator interfaces to create custom chaos behaviors.
package chaoskit
import (
"context"
"fmt"
"log/slog"
"os"
"time"
)
// recorderKey is a private type for context key
type recorderKey struct{}
type loggerKey struct{}
// Target represents the system under test.
// Implement this interface to define the system that will be subject to chaos testing.
//
// Example:
//
// type MySystem struct{}
//
// func (s *MySystem) Name() string { return "my-system" }
//
// func (s *MySystem) Setup(ctx context.Context) error {
// // Initialize system resources
// return nil
// }
//
// func (s *MySystem) Teardown(ctx context.Context) error {
// // Clean up resources
// return nil
// }
type Target interface {
Name() string
Setup(ctx context.Context) error
Teardown(ctx context.Context) error
}
// Step represents a single step in a scenario.
// Steps are executed sequentially and can use chaos injection functions
// like MaybeDelay() and MaybePanic() to interact with active injectors.
//
// Example:
//
// step := &myStep{name: "process-order"}
// scenario.Step("process", step.Execute)
type Step interface {
Name() string
Execute(ctx context.Context, target Target) error
}
// InjectorType defines how an injector applies its effects
type InjectorType int
const (
// InjectorTypeGlobal applies effects globally (CPU, Memory, Network proxies)
InjectorTypeGlobal InjectorType = iota
// InjectorTypeContext applies effects through context (Delay, Panic)
InjectorTypeContext
// InjectorTypeStep applies effects before/after steps
InjectorTypeStep
// InjectorTypeHybrid can work in multiple modes
InjectorTypeHybrid
)
// Injector introduces faults into the system.
// Implement this interface to create custom chaos injection behaviors.
//
// Inject() is called when the injector should start injecting faults.
// Stop() is called when the injector should stop and clean up.
//
// Example implementations:
// - DelayInjector: Adds random delays
// - PanicInjector: Triggers panics with probability
// - NetworkInjector: Introduces network latency/drops
//
// See the injectors package for reference implementations.
type Injector interface {
Name() string
Inject(ctx context.Context) error
Stop(ctx context.Context) error
}
// CategorizedInjector provides information about injector type
type CategorizedInjector interface {
Injector
Type() InjectorType
}
// GlobalInjector indicates that injector applies global effects
type GlobalInjector interface {
Injector
IsGlobal() bool
}
// StepInjector can inject faults before/after step execution
type StepInjector interface {
Injector
BeforeStep(ctx context.Context) error
AfterStep(ctx context.Context, err error) error
}
// ChaosProvider is a universal interface for context-based chaos injection
type ChaosProvider interface {
Name() string
Apply(ctx context.Context) bool
}
// ChaosDelayProvider provides delay injection capability
type ChaosDelayProvider interface {
Injector
GetChaosDelay(ctx context.Context) (time.Duration, bool)
}
// ChaosErrorProvider provides error injection capability
type ChaosErrorProvider interface {
Injector
ShouldReturnError() error
}
// ChaosPanicProvider provides panic injection capability
type ChaosPanicProvider interface {
Injector
ShouldChaosPanic() bool
GetPanicProbability() float64
}
// ChaosNetworkProvider provides network chaos injection capability
type ChaosNetworkProvider interface {
Injector
ShouldApplyNetworkChaos(host string, port int) bool
GetNetworkLatency(host string, port int) (time.Duration, bool)
ShouldDropConnection(host string, port int) bool
}
// ChaosContextCancellationProvider provides context cancellation capability
type ChaosContextCancellationProvider interface {
Injector
GetChaosContext(parent context.Context) (context.Context, context.CancelFunc)
GetCancellationProbability() float64
}
// NetworkInjectorLifecycle manages network proxy setup/teardown
type NetworkInjectorLifecycle interface {
Injector
SetupNetwork(ctx context.Context) error
TeardownNetwork(ctx context.Context) error
}
// MetricsProvider allows injectors to expose metrics
type MetricsProvider interface {
Injector
GetMetrics() map[string]interface{}
}
// Validator checks system invariants.
// Implement this interface to verify that the system maintains expected properties
// during chaos testing.
//
// Validate() is called after each scenario execution to check invariants.
// Return an error if the invariant is violated.
//
// Severity() returns the severity level of this validator for CI/CD integration.
// This determines how failures are categorized in test reports.
//
// Example implementations:
// - GoroutineLimit: Ensures goroutine count stays below threshold
// - RecursionDepthLimit: Verifies recursion depth doesn't exceed limit
// - NoSlowIteration: Detects slow loops
//
// See the validators package for reference implementations.
type Validator interface {
Name() string
Validate(ctx context.Context, target Target) error
// Severity returns the severity level of this validator
// Added in v1.x for CI/CD integration
Severity() ValidationSeverity
}
// Resettable is implemented by validators that need to reset state between iterations
type Resettable interface {
Reset()
}
// StepWrapper is implemented by validators that can wrap step execution.
// This allows validators to intercept and modify step behavior, such as
// adding timeouts, monitoring, or other cross-cutting concerns.
//
// WrapStep receives a step and returns a wrapped version that will be
// executed instead of the original step. The wrapper function receives
// the same context and target as the original step.
//
// Example use cases:
// - InfiniteLoopValidator: Wraps steps with timeout to detect hung steps
// - PerformanceMonitor: Wraps steps to measure execution time
type StepWrapper interface {
WrapStep(step Step) func(ctx context.Context, target Target) error
}
// Logger is deprecated. Use *slog.Logger instead.
// This type is kept for backward compatibility but will be removed in a future version.
type Logger interface {
Printf(format string, v ...any)
Println(v ...any)
}
// --- Event recording & context helpers ---
// PanicRecorder is implemented by validators that can record panics during execution.
type PanicRecorder interface {
RecordPanic(ctx context.Context)
}
// RecursionRecorder is implemented by validators that can record recursion/rollback depth.
type RecursionRecorder interface {
RecordRecursion(depth int)
}
// ErrorRecorder is implemented by validators that can record errors during execution.
type ErrorRecorder interface {
RecordError(ctx context.Context)
}
// EventRecorder provides a unified interface for recording runtime events from steps.
type EventRecorder interface {
RecordPanic(ctx context.Context)
RecordRecursionDepth(depth int)
RecordError(ctx context.Context)
}
// AttachRecorder attaches an EventRecorder to context.
func AttachRecorder(ctx context.Context, r EventRecorder) context.Context {
return context.WithValue(ctx, recorderKey{}, r)
}
// RecordPanic records a panic via context-attached recorder (no-op if absent).
func RecordPanic(ctx context.Context) {
if v := ctx.Value(recorderKey{}); v != nil {
if r, ok := v.(EventRecorder); ok {
r.RecordPanic(ctx)
}
}
}
// RecordRecursionDepth records recursion depth via context-attached recorder (no-op if absent).
func RecordRecursionDepth(ctx context.Context, depth int) {
if v := ctx.Value(recorderKey{}); v != nil {
if r, ok := v.(EventRecorder); ok {
r.RecordRecursionDepth(depth)
}
}
}
// RecordError records an error via context-attached recorder (no-op if absent).
func RecordError(ctx context.Context) {
if v := ctx.Value(recorderKey{}); v != nil {
if r, ok := v.(EventRecorder); ok {
r.RecordError(ctx)
}
}
}
// AttachLogger attaches a logger to context.
func AttachLogger(ctx context.Context, logger *slog.Logger) context.Context {
return context.WithValue(ctx, loggerKey{}, logger)
}
// GetLogger retrieves logger from context, or returns slog.Default() if not found.
func GetLogger(ctx context.Context) *slog.Logger {
if v := ctx.Value(loggerKey{}); v != nil {
if logger, ok := v.(*slog.Logger); ok {
return logger
}
}
return slog.Default()
}
// Run executes a scenario with default settings.
// This is a convenience function that creates an executor, runs the scenario,
// and prints the report.
//
// Example:
//
// scenario := chaoskit.NewScenario("test").WithTarget(mySystem).Build()
// if err := chaoskit.Run(ctx, scenario); err != nil {
// log.Fatal(err)
// }
func Run(ctx context.Context, scenario *Scenario) error {
executor := NewExecutor()
if err := executor.Run(ctx, scenario); err != nil {
return err
}
fmt.Println(executor.Reporter().GenerateReport())
return nil
}
// RunWithLogger executes a scenario with a custom logger (deprecated, use RunWithSlogLogger)
func RunWithLogger(ctx context.Context, scenario *Scenario, logger Logger) error {
// Convert old Logger to slog.Logger for backward compatibility
slogLogger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
executor := NewExecutor(WithSlogLogger(slogLogger))
if err := executor.Run(ctx, scenario); err != nil {
return err
}
logger.Println(executor.Reporter().GenerateReport())
return nil
}
// RunWithSlogLogger executes a scenario with a structured logger.
// Use this function when you want to use structured logging with slog.
//
// Example:
//
// logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// scenario := chaoskit.NewScenario("test").WithTarget(mySystem).Build()
// if err := chaoskit.RunWithSlogLogger(ctx, scenario, logger); err != nil {
// log.Fatal(err)
// }
func RunWithSlogLogger(ctx context.Context, scenario *Scenario, logger *slog.Logger) error {
executor := NewExecutor(WithSlogLogger(logger))
if err := executor.Run(ctx, scenario); err != nil {
return err
}
logger.Info(executor.Reporter().GenerateReport())
return nil
}
// defaultLogger wraps standard log package (deprecated)
type defaultLogger struct{}
func (d *defaultLogger) Printf(format string, v ...any) {
slog.Default().Info(fmt.Sprintf(format, v...))
}
func (d *defaultLogger) Println(v ...any) {
slog.Default().Info(fmt.Sprint(v...))
}
// NewDefaultLogger creates a default logger (deprecated, use slog.Default())
func NewDefaultLogger() Logger {
return &defaultLogger{}
}