@assertrx/dino 1.0.9 → 1.0.11
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.
- package/package.json +1 -1
- package/prompts/generate-bdd-from-url.md +5 -1
- package/prompts/generate-k6-test-from-endpoint.md +68 -0
- package/prompts/generate-k6-test-from-openapi.md +85 -0
- package/prompts/setup-k6.md +130 -0
- package/scripts/api-server.mjs +32 -2
- package/skills/analyzing-test-results/SKILL.md +150 -0
- package/skills/analyzing-test-results/reference/bottleneck-patterns.md +130 -0
- package/skills/analyzing-test-results/reference/metrics.md +134 -0
- package/skills/analyzing-test-results/reference/visualization.md +152 -0
- package/skills/designing-test-scenarios/SKILL.md +247 -0
- package/skills/designing-test-scenarios/reference/executors.md +215 -0
- package/skills/designing-test-scenarios/reference/test-types.md +215 -0
- package/skills/designing-test-scenarios/reference/thresholds.md +208 -0
- package/skills/generating-api-load-tests/SKILL.md +199 -0
- package/skills/generating-api-load-tests/reference/data-parameterization.md +343 -0
- package/skills/generating-api-load-tests/reference/grpc.md +315 -0
- package/skills/generating-api-load-tests/reference/http.md +380 -0
- package/skills/generating-api-load-tests/reference/websocket.md +369 -0
- package/skills/generating-browser-tests/SKILL.md +269 -0
- package/skills/generating-browser-tests/reference/browser-api.md +251 -0
- package/skills/generating-browser-tests/reference/web-vitals.md +148 -0
- package/skills/generating-tests-from-code/SKILL.md +142 -0
- package/skills/generating-tests-from-code/reference/auth-analysis.md +220 -0
- package/skills/generating-tests-from-code/reference/framework-patterns.md +242 -0
- package/skills/generating-tests-from-openapi/SKILL.md +234 -0
- package/skills/generating-tests-from-openapi/reference/openapi-auth-mapping.md +247 -0
- package/skills/generating-tests-from-openapi/reference/openapi-parsing.md +194 -0
- package/skills/operating-k6-in-ci-cd/SKILL.md +98 -0
- package/skills/operating-k6-in-ci-cd/reference/ci-cd-pipelines.md +265 -0
- package/skills/operating-k6-in-ci-cd/reference/extensions.md +144 -0
- package/skills/operating-k6-in-ci-cd/reference/k6-operator.md +214 -0
- package/skills/testing-resilience/SKILL.md +230 -0
- package/skills/testing-resilience/reference/fault-injection.md +153 -0
- package/skills/testing-resilience/reference/resilience-patterns.md +500 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing-resilience
|
|
3
|
+
description: Use when creating chaos engineering or resilience tests with k6 and xk6-disruptor. Use when the user wants to inject faults (pod failures, network delays, HTTP errors) into Kubernetes services during load tests, validate circuit breakers, or test system recovery behavior.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Testing Resilience with k6
|
|
7
|
+
|
|
8
|
+
Create chaos and resilience tests using xk6-disruptor to inject faults into Kubernetes services during k6 load tests. Validates system behavior under failure conditions.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
xk6-disruptor injects faults into Kubernetes pods and services while k6 generates load, allowing you to verify:
|
|
13
|
+
- System survives pod failures
|
|
14
|
+
- Graceful degradation under network issues
|
|
15
|
+
- Circuit breaker behavior
|
|
16
|
+
- Recovery time after fault removal
|
|
17
|
+
- Auto-scaling response to failures
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
import http from 'k6/http';
|
|
23
|
+
import { check, sleep } from 'k6';
|
|
24
|
+
import { ServiceDisruptor } from 'k6/x/disruptor';
|
|
25
|
+
|
|
26
|
+
export const options = {
|
|
27
|
+
scenarios: {
|
|
28
|
+
load: {
|
|
29
|
+
executor: 'constant-arrival-rate',
|
|
30
|
+
rate: 100,
|
|
31
|
+
timeUnit: '1s',
|
|
32
|
+
duration: '2m',
|
|
33
|
+
preAllocatedVUs: 50,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
thresholds: {
|
|
37
|
+
http_req_failed: ['rate<0.3'], // Allow up to 30% errors during chaos
|
|
38
|
+
http_req_duration: ['p(95)<3000'], // Relaxed threshold during chaos
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function setup() {
|
|
43
|
+
// Inject fault: Add 500ms delay to 50% of requests
|
|
44
|
+
const disruptor = new ServiceDisruptor('my-service', 'default');
|
|
45
|
+
const fault = {
|
|
46
|
+
averageDelay: '500ms',
|
|
47
|
+
errorRate: 0.1, // 10% of requests return errors
|
|
48
|
+
errorCode: 503,
|
|
49
|
+
};
|
|
50
|
+
disruptor.injectHTTPFaults(fault, '60s');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default function () {
|
|
54
|
+
const res = http.get('http://my-service.default.svc.cluster.local:8080/api');
|
|
55
|
+
check(res, {
|
|
56
|
+
'not server error': (r) => r.status < 500 || r.status === 503,
|
|
57
|
+
});
|
|
58
|
+
sleep(0.1); // Intentional: adds backpressure per VU to avoid overwhelming the disrupted service
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Prerequisites
|
|
63
|
+
|
|
64
|
+
- **Kubernetes cluster** with target services deployed
|
|
65
|
+
- **k6 with xk6-disruptor** extension (or use automatic resolution)
|
|
66
|
+
- **RBAC permissions** for the k6 pod to manage target pods
|
|
67
|
+
|
|
68
|
+
## Disruptor Types
|
|
69
|
+
|
|
70
|
+
### ServiceDisruptor
|
|
71
|
+
|
|
72
|
+
Targets all pods behind a Kubernetes Service:
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
import { ServiceDisruptor } from 'k6/x/disruptor';
|
|
76
|
+
|
|
77
|
+
const disruptor = new ServiceDisruptor('service-name', 'namespace');
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### PodDisruptor
|
|
81
|
+
|
|
82
|
+
Targets specific pods by label selector:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
import { PodDisruptor } from 'k6/x/disruptor';
|
|
86
|
+
|
|
87
|
+
const disruptor = new PodDisruptor({
|
|
88
|
+
namespace: 'default',
|
|
89
|
+
select: { labels: { app: 'my-app' } },
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Fault Types
|
|
94
|
+
|
|
95
|
+
### HTTP Faults
|
|
96
|
+
|
|
97
|
+
Inject delays and errors into HTTP responses:
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
disruptor.injectHTTPFaults(
|
|
101
|
+
{
|
|
102
|
+
averageDelay: '500ms', // Add delay to responses
|
|
103
|
+
delayVariation: '100ms', // Delay jitter
|
|
104
|
+
errorRate: 0.1, // 10% error rate
|
|
105
|
+
errorCode: 503, // Error HTTP status code
|
|
106
|
+
port: 8080, // Target port
|
|
107
|
+
exclude: '/health', // Exclude health check endpoint
|
|
108
|
+
},
|
|
109
|
+
'60s' // Fault duration
|
|
110
|
+
);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### gRPC Faults
|
|
114
|
+
|
|
115
|
+
Inject faults into gRPC services:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
disruptor.injectGrpcFaults(
|
|
119
|
+
{
|
|
120
|
+
averageDelay: '500ms',
|
|
121
|
+
errorRate: 0.2,
|
|
122
|
+
statusCode: 14, // UNAVAILABLE
|
|
123
|
+
statusMessage: 'service overloaded',
|
|
124
|
+
},
|
|
125
|
+
'60s'
|
|
126
|
+
);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Pod Termination
|
|
130
|
+
|
|
131
|
+
Kill pods to test recovery:
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
disruptor.terminatePods({
|
|
135
|
+
count: 1, // Number of pods to terminate
|
|
136
|
+
interval: '30s', // Wait between terminations
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
See [reference/fault-injection.md](reference/fault-injection.md) for detailed fault configuration.
|
|
141
|
+
|
|
142
|
+
## Resilience Test Patterns
|
|
143
|
+
|
|
144
|
+
### Pattern 1: Graceful Degradation
|
|
145
|
+
|
|
146
|
+
Verify system degrades gracefully under partial failure:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
export function setup() {
|
|
150
|
+
const disruptor = new ServiceDisruptor('backend-service', 'default');
|
|
151
|
+
disruptor.injectHTTPFaults({
|
|
152
|
+
averageDelay: '1000ms',
|
|
153
|
+
errorRate: 0.2,
|
|
154
|
+
errorCode: 503,
|
|
155
|
+
}, '90s');
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Success criteria:** System returns degraded responses instead of cascading failures.
|
|
160
|
+
|
|
161
|
+
### Pattern 2: Circuit Breaker Validation
|
|
162
|
+
|
|
163
|
+
Verify circuit breaker activates under error conditions:
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
export function setup() {
|
|
167
|
+
const disruptor = new ServiceDisruptor('downstream-service', 'default');
|
|
168
|
+
disruptor.injectHTTPFaults({
|
|
169
|
+
errorRate: 0.8, // 80% errors to trigger circuit breaker
|
|
170
|
+
errorCode: 500,
|
|
171
|
+
}, '60s');
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Success criteria:** Upstream service returns fallback responses quickly (circuit open).
|
|
176
|
+
|
|
177
|
+
### Pattern 3: Recovery Test
|
|
178
|
+
|
|
179
|
+
Verify system recovers after fault is removed:
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
export const options = {
|
|
183
|
+
scenarios: {
|
|
184
|
+
load: {
|
|
185
|
+
executor: 'constant-arrival-rate',
|
|
186
|
+
rate: 50,
|
|
187
|
+
timeUnit: '1s',
|
|
188
|
+
duration: '5m', // Total test: 5 minutes
|
|
189
|
+
preAllocatedVUs: 30,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export function setup() {
|
|
195
|
+
const disruptor = new ServiceDisruptor('my-service', 'default');
|
|
196
|
+
// Inject fault for only 2 minutes (test runs 5 minutes)
|
|
197
|
+
disruptor.injectHTTPFaults({
|
|
198
|
+
averageDelay: '2000ms',
|
|
199
|
+
errorRate: 0.5,
|
|
200
|
+
errorCode: 503,
|
|
201
|
+
}, '120s'); // Fault active for 2m, then 3m of recovery
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Success criteria:** Metrics return to baseline within acceptable time after fault removal.
|
|
206
|
+
|
|
207
|
+
See [reference/resilience-patterns.md](reference/resilience-patterns.md) for more patterns.
|
|
208
|
+
|
|
209
|
+
## Thresholds for Resilience Tests
|
|
210
|
+
|
|
211
|
+
Resilience tests need relaxed thresholds compared to normal load tests. Use tag-based thresholds to set different criteria for chaos and recovery phases:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
export const options = {
|
|
215
|
+
thresholds: {
|
|
216
|
+
// During chaos: accept higher error rates
|
|
217
|
+
http_req_failed: ['rate<0.5'], // Up to 50% errors acceptable
|
|
218
|
+
// Overall response time upper bound (includes chaos phase)
|
|
219
|
+
http_req_duration: ['p(95)<5000'], // 5s max even during chaos
|
|
220
|
+
// Strict threshold for recovery phase only (tagged requests)
|
|
221
|
+
'http_req_duration{phase:recovery}': ['p(95)<500'], // After recovery, back to normal
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Related Skills
|
|
227
|
+
|
|
228
|
+
- For generating load test scripts: `/k6:generating-api-load-tests`
|
|
229
|
+
- For CI/CD and Kubernetes setup: `/k6:operating-k6-in-ci-cd`
|
|
230
|
+
- For scenario design: `/k6:designing-test-scenarios`
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Fault Injection Reference
|
|
2
|
+
|
|
3
|
+
## ServiceDisruptor
|
|
4
|
+
|
|
5
|
+
Targets all pods behind a Kubernetes Service.
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { ServiceDisruptor } from 'k6/x/disruptor';
|
|
9
|
+
|
|
10
|
+
const disruptor = new ServiceDisruptor('service-name', 'namespace');
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### injectHTTPFaults(fault, duration)
|
|
14
|
+
|
|
15
|
+
| Parameter | Type | Description |
|
|
16
|
+
|-----------|------|-------------|
|
|
17
|
+
| `averageDelay` | string | Average delay added to responses (e.g., `'500ms'`) |
|
|
18
|
+
| `delayVariation` | string | Variation around averageDelay (e.g., `'100ms'`) |
|
|
19
|
+
| `errorRate` | float | Fraction of requests returning errors (0.0-1.0) |
|
|
20
|
+
| `errorCode` | integer | HTTP status code for injected errors |
|
|
21
|
+
| `errorBody` | string | Response body for injected errors |
|
|
22
|
+
| `port` | integer | Target port (required if service has multiple ports) |
|
|
23
|
+
| `exclude` | string | URL path prefix to exclude from faults |
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
disruptor.injectHTTPFaults({
|
|
27
|
+
averageDelay: '500ms',
|
|
28
|
+
delayVariation: '100ms',
|
|
29
|
+
errorRate: 0.1,
|
|
30
|
+
errorCode: 503,
|
|
31
|
+
errorBody: '{"error": "service unavailable"}',
|
|
32
|
+
port: 8080,
|
|
33
|
+
exclude: '/health',
|
|
34
|
+
}, '60s');
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### injectGrpcFaults(fault, duration)
|
|
38
|
+
|
|
39
|
+
| Parameter | Type | Description |
|
|
40
|
+
|-----------|------|-------------|
|
|
41
|
+
| `averageDelay` | string | Average delay added to responses |
|
|
42
|
+
| `delayVariation` | string | Variation around averageDelay |
|
|
43
|
+
| `errorRate` | float | Fraction of requests returning errors (0.0-1.0) |
|
|
44
|
+
| `statusCode` | integer | gRPC status code for errors |
|
|
45
|
+
| `statusMessage` | string | gRPC status message for errors |
|
|
46
|
+
| `port` | integer | Target port |
|
|
47
|
+
| `exclude` | string | Service method to exclude |
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
disruptor.injectGrpcFaults({
|
|
51
|
+
averageDelay: '500ms',
|
|
52
|
+
errorRate: 0.2,
|
|
53
|
+
statusCode: 14, // UNAVAILABLE
|
|
54
|
+
statusMessage: 'injected fault',
|
|
55
|
+
}, '60s');
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### terminatePods(options)
|
|
59
|
+
|
|
60
|
+
| Parameter | Type | Description |
|
|
61
|
+
|-----------|------|-------------|
|
|
62
|
+
| `count` | integer | Number of pods to terminate |
|
|
63
|
+
| `interval` | string | Wait time between terminations |
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
disruptor.terminatePods({
|
|
67
|
+
count: 1,
|
|
68
|
+
interval: '30s',
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## PodDisruptor
|
|
73
|
+
|
|
74
|
+
Targets pods by label selector.
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
import { PodDisruptor } from 'k6/x/disruptor';
|
|
78
|
+
|
|
79
|
+
const disruptor = new PodDisruptor({
|
|
80
|
+
namespace: 'default',
|
|
81
|
+
select: {
|
|
82
|
+
labels: { app: 'my-app', version: 'v1' },
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Same methods as ServiceDisruptor: `injectHTTPFaults()`, `injectGrpcFaults()`, `terminatePods()`.
|
|
88
|
+
|
|
89
|
+
Additional method:
|
|
90
|
+
|
|
91
|
+
### targets()
|
|
92
|
+
|
|
93
|
+
Returns list of target pod names:
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
const pods = disruptor.targets();
|
|
97
|
+
console.log(`Targeting ${pods.length} pods:`, pods);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Kubernetes Requirements
|
|
101
|
+
|
|
102
|
+
### RBAC
|
|
103
|
+
|
|
104
|
+
The k6 pod needs permissions to manage target pods:
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
108
|
+
kind: ClusterRole
|
|
109
|
+
metadata:
|
|
110
|
+
name: k6-disruptor
|
|
111
|
+
rules:
|
|
112
|
+
- apiGroups: [""]
|
|
113
|
+
resources: ["pods", "pods/ephemeralcontainers"]
|
|
114
|
+
verbs: ["get", "list", "patch", "update", "delete"]
|
|
115
|
+
- apiGroups: [""]
|
|
116
|
+
resources: ["services"]
|
|
117
|
+
verbs: ["get", "list"]
|
|
118
|
+
- apiGroups: ["apps"]
|
|
119
|
+
resources: ["deployments"]
|
|
120
|
+
verbs: ["get", "list"]
|
|
121
|
+
---
|
|
122
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
123
|
+
kind: ClusterRoleBinding
|
|
124
|
+
metadata:
|
|
125
|
+
name: k6-disruptor-binding
|
|
126
|
+
roleRef:
|
|
127
|
+
apiGroup: rbac.authorization.k8s.io
|
|
128
|
+
kind: ClusterRole
|
|
129
|
+
name: k6-disruptor
|
|
130
|
+
subjects:
|
|
131
|
+
- kind: ServiceAccount
|
|
132
|
+
name: default
|
|
133
|
+
namespace: k6-test
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Running in Cluster
|
|
137
|
+
|
|
138
|
+
For xk6-disruptor to work, k6 must run **inside the Kubernetes cluster** (not locally). Options:
|
|
139
|
+
- k6-operator TestRun
|
|
140
|
+
- Pod with k6 + xk6-disruptor image
|
|
141
|
+
- CI/CD runner with cluster access
|
|
142
|
+
|
|
143
|
+
## Fault Duration vs Test Duration
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Test Duration: |========================== 5 min ============================|
|
|
147
|
+
Fault Duration: |======== 2 min ========|
|
|
148
|
+
↑ Fault injected ↑ Fault removed ↑ Test ends
|
|
149
|
+
|
|
150
|
+
Phases: |--- Chaos Phase ------|--- Recovery Phase ---|
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Set fault duration shorter than test duration to observe both degradation and recovery.
|