-
Notifications
You must be signed in to change notification settings - Fork 108
/
httpprobe.go
456 lines (406 loc) · 18 KB
/
httpprobe.go
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
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
package probe
import (
"bytes"
"fmt"
"github.com/litmuschaos/litmus-go/pkg/utils"
"os/exec"
"reflect"
"strconv"
"strings"
"time"
"crypto/tls"
"net/http"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
"github.com/litmuschaos/litmus-go/pkg/cerrors"
"github.com/litmuschaos/litmus-go/pkg/clients"
"github.com/litmuschaos/litmus-go/pkg/log"
"github.com/litmuschaos/litmus-go/pkg/math"
cmp "github.com/litmuschaos/litmus-go/pkg/probe/comparator"
"github.com/litmuschaos/litmus-go/pkg/types"
"github.com/litmuschaos/litmus-go/pkg/utils/retry"
"github.com/sirupsen/logrus"
)
// prepareHTTPProbe contains the steps to prepare the http probe
// http probe can be used to add the probe which will send a request to given url and match the status code
func prepareHTTPProbe(probe v1alpha1.ProbeAttributes, clients clients.ClientSets, chaosDetails *types.ChaosDetails, resultDetails *types.ResultDetails, phase string) error {
switch strings.ToLower(phase) {
case "prechaos":
if err := preChaosHTTPProbe(probe, resultDetails, clients, chaosDetails); err != nil {
return err
}
case "postchaos":
if err := postChaosHTTPProbe(probe, resultDetails, chaosDetails.Delay, chaosDetails.Timeout); err != nil {
return err
}
case "duringchaos":
onChaosHTTPProbe(probe, resultDetails, clients, chaosDetails)
default:
return cerrors.Error{ErrorCode: cerrors.ErrorTypeHttpProbe, Target: fmt.Sprintf("{name: %v}", probe.Name), Reason: fmt.Sprintf("phase '%s' not supported in the http probe", phase)}
}
return nil
}
// triggerHTTPProbe run the http probe command
func triggerHTTPProbe(probe v1alpha1.ProbeAttributes, resultDetails *types.ResultDetails) error {
probeTimeout := getProbeTimeouts(probe.Name, resultDetails.ProbeDetails)
// It parses the templated url and return normal string
// if command doesn't have template, it will return the same command
probe.HTTPProbeInputs.URL, err = parseCommand(probe.HTTPProbeInputs.URL, resultDetails)
if err != nil {
return err
}
// it fetches the http method type
method := getHTTPMethodType(probe.HTTPProbeInputs.Method)
// initialize simple http client with default attributes
client := &http.Client{Timeout: probeTimeout.ProbeTimeout}
// impose properties to http client with cert check disabled
if probe.HTTPProbeInputs.InsecureSkipVerify {
transCfg := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client = &http.Client{Transport: transCfg, Timeout: probeTimeout.ProbeTimeout}
}
switch method {
case "Get":
log.InfoWithValues("[Probe]: HTTP get method informations", logrus.Fields{
"Name": probe.Name,
"URL": probe.HTTPProbeInputs.URL,
"Criteria": probe.HTTPProbeInputs.Method.Get.Criteria,
"ResponseCode": probe.HTTPProbeInputs.Method.Get.ResponseCode,
"ResponseTimeout": probe.RunProperties.ProbeTimeout,
})
return httpGet(probe, client, resultDetails)
case "Post":
log.InfoWithValues("[Probe]: HTTP Post method informations", logrus.Fields{
"Name": probe.Name,
"URL": probe.HTTPProbeInputs.URL,
"Body": probe.HTTPProbeInputs.Method.Post.Body,
"BodyPath": probe.HTTPProbeInputs.Method.Post.BodyPath,
"ContentType": probe.HTTPProbeInputs.Method.Post.ContentType,
"ResponseTimeout": probe.RunProperties.ProbeTimeout,
})
return httpPost(probe, client, resultDetails)
}
return nil
}
// it fetches the http method type
// it supports Get and Post methods
func getHTTPMethodType(httpMethod v1alpha1.HTTPMethod) string {
if !reflect.DeepEqual(httpMethod.Get, v1alpha1.GetMethod{}) {
return "Get"
}
return "Post"
}
// httpGet send the http Get request to the given URL and verify the response code to follow the specified criteria
func httpGet(probe v1alpha1.ProbeAttributes, client *http.Client, resultDetails *types.ResultDetails) error {
probeTimeout := getProbeTimeouts(probe.Name, resultDetails.ProbeDetails)
var description string
// it will retry for some retry count, in each iteration of try it contains following things
// it contains a timeout per iteration of retry. if the timeout expires without success then it will go to next try
// for a timeout, it will run the command, if it fails wait for the interval and again execute the command until timeout expires
if err := retry.Times(uint(getAttempts(probe.RunProperties.Attempt, probe.RunProperties.Retry))).
Wait(probeTimeout.Interval).
Try(func(attempt uint) error {
// getting the response from the given url
resp, err := client.Get(probe.HTTPProbeInputs.URL)
if err != nil {
if utils.HttpTimeout(err) {
return cerrors.Error{ErrorCode: cerrors.FailureTypeHttpProbe, Target: fmt.Sprintf("{name: %v}", probe.Name), Reason: err.Error()}
}
return cerrors.Error{ErrorCode: cerrors.ErrorTypeHttpProbe, Target: fmt.Sprintf("{name: %v}", probe.Name), Reason: err.Error()}
}
code := strconv.Itoa(resp.StatusCode)
rc := getAndIncrementRunCount(resultDetails, probe.Name)
// comparing the response code with the expected criteria
if err = cmp.RunCount(rc).
FirstValue(code).
SecondValue(probe.HTTPProbeInputs.Method.Get.ResponseCode).
Criteria(probe.HTTPProbeInputs.Method.Get.Criteria).
ProbeName(probe.Name).
ProbeVerbosity(probe.RunProperties.Verbosity).
CompareInt(cerrors.FailureTypeHttpProbe); err != nil {
log.Errorf("The %v http probe get method has Failed, err: %v", probe.Name, err)
return err
}
description = fmt.Sprintf("The URL %s did respond with correct status code. Actual code: '%s'. Expected code: '%s'", probe.HTTPProbeInputs.URL, code, probe.HTTPProbeInputs.Method.Get.ResponseCode)
return nil
}); err != nil {
return err
}
setProbeDescription(resultDetails, probe, description)
return nil
}
// httpPost send the http post request to the given URL
func httpPost(probe v1alpha1.ProbeAttributes, client *http.Client, resultDetails *types.ResultDetails) error {
probeTimeout := getProbeTimeouts(probe.Name, resultDetails.ProbeDetails)
body, err := getHTTPBody(probe.HTTPProbeInputs.Method.Post, probe.Name)
if err != nil {
return err
}
var description string
// it will retry for some retry count, in each iteration of try it contains following things
// it contains a timeout per iteration of retry. if the timeout expires without success then it will go to next try
// for a timeout, it will run the command, if it fails wait for the interval and again execute the command until timeout expires
if err := retry.Times(uint(getAttempts(probe.RunProperties.Attempt, probe.RunProperties.Retry))).
Wait(probeTimeout.Interval).
Try(func(attempt uint) error {
resp, err := client.Post(probe.HTTPProbeInputs.URL, probe.HTTPProbeInputs.Method.Post.ContentType, strings.NewReader(body))
if err != nil {
if utils.HttpTimeout(err) {
return cerrors.Error{ErrorCode: cerrors.FailureTypeHttpProbe, Target: fmt.Sprintf("{name: %v}", probe.Name), Reason: err.Error()}
}
return cerrors.Error{ErrorCode: cerrors.ErrorTypeHttpProbe, Target: fmt.Sprintf("{name: %v}", probe.Name), Reason: err.Error()}
}
code := strconv.Itoa(resp.StatusCode)
rc := getAndIncrementRunCount(resultDetails, probe.Name)
// comparing the response code with the expected criteria
if err = cmp.RunCount(rc).
FirstValue(code).
SecondValue(probe.HTTPProbeInputs.Method.Post.ResponseCode).
Criteria(probe.HTTPProbeInputs.Method.Post.Criteria).
ProbeName(probe.Name).
ProbeVerbosity(probe.RunProperties.Verbosity).
CompareInt(cerrors.FailureTypeHttpProbe); err != nil {
log.Errorf("The %v http probe post method has Failed, err: %v", probe.Name, err)
return err
}
description = fmt.Sprintf("The URL %s did respond with correct status code. Actual code: '%s'. Expected code: '%s'", probe.HTTPProbeInputs.URL, code, probe.HTTPProbeInputs.Method.Get.ResponseCode)
return nil
}); err != nil {
return err
}
setProbeDescription(resultDetails, probe, description)
return nil
}
// getHTTPBody fetch the http body for the post request
// It will use body or bodyPath attributes to get the http request body
// if both are provided, it will use body field
func getHTTPBody(httpBody *v1alpha1.PostMethod, probeName string) (string, error) {
if httpBody.Body != "" {
return httpBody.Body, nil
}
var command string
if httpBody.BodyPath != "" {
command = "cat " + httpBody.BodyPath
} else {
return "", cerrors.Error{ErrorCode: cerrors.ErrorTypeHttpProbe, Target: fmt.Sprintf("{name: %v}", probeName), Reason: "[Probe]: Any one of body or bodyPath is required"}
}
var out, errOut bytes.Buffer
// run the inline command probe
cmd := exec.Command("/bin/sh", "-c", command)
cmd.Stdout = &out
cmd.Stderr = &errOut
if err := cmd.Run(); err != nil {
return "", cerrors.Error{ErrorCode: cerrors.ErrorTypeHttpProbe, Target: fmt.Sprintf("{name: %v}", probeName), Reason: fmt.Sprintf("unable to run command, err: %v; error output: %v", err, errOut.String())}
}
return out.String(), nil
}
// triggerContinuousHTTPProbe trigger the continuous http probes
func triggerContinuousHTTPProbe(probe v1alpha1.ProbeAttributes, clients clients.ClientSets, chaosresult *types.ResultDetails, chaosDetails *types.ChaosDetails) {
probeTimeout := getProbeTimeouts(probe.Name, chaosresult.ProbeDetails)
var isExperimentFailed bool
// waiting for initial delay
if probeTimeout.InitialDelay != 0 {
log.Infof("[Wait]: Waiting for %v before probe execution", probe.RunProperties.InitialDelay)
time.Sleep(probeTimeout.InitialDelay)
}
// it triggers the http probe for the entire duration of chaos and it fails, if any error encounter
// it marked the error for the probes, if any
loop:
for {
select {
case <-chaosDetails.ProbeContext.Ctx.Done():
log.Infof("Stopping %s continuous Probe", probe.Name)
for index := range chaosresult.ProbeDetails {
if chaosresult.ProbeDetails[index].Name == probe.Name {
chaosresult.ProbeDetails[index].HasProbeCompleted = true
}
}
break loop
default:
err = triggerHTTPProbe(probe, chaosresult)
// record the error inside the probeDetails, we are maintaining a dedicated variable for the err, inside probeDetails
if err != nil {
err = addProbePhase(err, string(chaosDetails.Phase))
for index := range chaosresult.ProbeDetails {
if chaosresult.ProbeDetails[index].Name == probe.Name {
chaosresult.ProbeDetails[index].IsProbeFailedWithError = err
chaosresult.ProbeDetails[index].HasProbeCompleted = true
chaosresult.ProbeDetails[index].Status.Description = getDescription(err)
log.Errorf("The %v http probe has been Failed, err: %v", probe.Name, err)
isExperimentFailed = true
break loop
}
}
}
// waiting for the probe polling interval
time.Sleep(probeTimeout.ProbePollingInterval)
}
}
// if experiment fails and stopOnfailure is provided as true then it will patch the chaosengine for abort
// if experiment fails but stopOnfailure is provided as false then it will continue the execution
// and failed the experiment in the end
if isExperimentFailed && probe.RunProperties.StopOnFailure {
if err := stopChaosEngine(probe, clients, chaosresult, chaosDetails); err != nil {
log.Errorf("Unable to patch chaosengine to stop, err: %v", err)
}
}
}
// preChaosHTTPProbe trigger the http probe for prechaos phase
func preChaosHTTPProbe(probe v1alpha1.ProbeAttributes, resultDetails *types.ResultDetails, clients clients.ClientSets, chaosDetails *types.ChaosDetails) error {
probeTimeout := getProbeTimeouts(probe.Name, resultDetails.ProbeDetails)
switch probe.Mode {
case "SOT", "Edge":
//DISPLAY THE HTTP PROBE INFO
log.InfoWithValues("[Probe]: The http probe information is as follows", logrus.Fields{
"Name": probe.Name,
"URL": probe.HTTPProbeInputs.URL,
"Run Properties": probe.RunProperties,
"Mode": probe.Mode,
"Phase": "PreChaos",
})
// waiting for initial delay
if probeTimeout.InitialDelay != 0 {
log.Infof("[Wait]: Waiting for %v before probe execution", probe.RunProperties.InitialDelay)
time.Sleep(probeTimeout.InitialDelay)
}
// trigger the http probe
if err = triggerHTTPProbe(probe, resultDetails); err != nil && cerrors.GetErrorType(err) != cerrors.FailureTypeHttpProbe {
return err
}
// failing the probe, if the success condition doesn't met after the retry & timeout combinations
// it will update the status of all the unrun probes as well
if err = markedVerdictInEnd(err, resultDetails, probe, "PreChaos"); err != nil {
return err
}
case "Continuous":
//DISPLAY THE HTTP PROBE INFO
log.InfoWithValues("[Probe]: The http probe information is as follows", logrus.Fields{
"Name": probe.Name,
"URL": probe.HTTPProbeInputs.URL,
"Run Properties": probe.RunProperties,
"Mode": probe.Mode,
"Phase": "PreChaos",
})
go triggerContinuousHTTPProbe(probe, clients, resultDetails, chaosDetails)
}
return nil
}
// postChaosHTTPProbe trigger the http probe for postchaos phase
func postChaosHTTPProbe(probe v1alpha1.ProbeAttributes, resultDetails *types.ResultDetails, delay int, timeout int) error {
probeTimeout := getProbeTimeouts(probe.Name, resultDetails.ProbeDetails)
switch probe.Mode {
case "EOT", "Edge":
//DISPLAY THE HTTP PROBE INFO
log.InfoWithValues("[Probe]: The http probe information is as follows", logrus.Fields{
"Name": probe.Name,
"URL": probe.HTTPProbeInputs.URL,
"Run Properties": probe.RunProperties,
"Mode": probe.Mode,
"Phase": "PostChaos",
})
// waiting for initial delay
if probeTimeout.InitialDelay != 0 {
log.Infof("[Wait]: Waiting for %v before probe execution", probe.RunProperties.InitialDelay)
time.Sleep(probeTimeout.InitialDelay)
}
// trigger the http probe
if err = triggerHTTPProbe(probe, resultDetails); err != nil && cerrors.GetErrorType(err) != cerrors.FailureTypeHttpProbe {
return err
}
// failing the probe, if the success condition doesn't met after the retry & timeout combinations
// it will update the status of all the unrun probes as well
if err = markedVerdictInEnd(err, resultDetails, probe, "PostChaos"); err != nil {
return err
}
case "Continuous", "OnChaos":
// it will check for the error, It will detect the error if any error encountered in probe during chaos
if err = checkForErrorInContinuousProbe(resultDetails, probe.Name, delay, timeout); err != nil && cerrors.GetErrorType(err) != cerrors.FailureTypeHttpProbe && cerrors.GetErrorType(err) != cerrors.FailureTypeProbeTimeout {
return err
}
// failing the probe, if the success condition doesn't met after the retry & timeout combinations
if err = markedVerdictInEnd(err, resultDetails, probe, "PostChaos"); err != nil {
return err
}
}
return nil
}
// triggerOnChaosHTTPProbe trigger the onchaos http probes
func triggerOnChaosHTTPProbe(probe v1alpha1.ProbeAttributes, clients clients.ClientSets, chaosresult *types.ResultDetails, chaosDetails *types.ChaosDetails) {
probeTimeout := getProbeTimeouts(probe.Name, chaosresult.ProbeDetails)
var isExperimentFailed bool
duration := chaosDetails.ChaosDuration
// waiting for initial delay
if probeTimeout.InitialDelay != 0 {
log.Infof("[Wait]: Waiting for %v before probe execution", probe.RunProperties.InitialDelay)
time.Sleep(probeTimeout.InitialDelay)
duration = math.Maximum(0, duration-int(probeTimeout.InitialDelay.Seconds()))
}
endTime := time.After(time.Duration(duration) * time.Second)
// it trigger the http probe for the entire duration of chaos and it fails, if any error encounter
// it marked the error for the probes, if any
loop:
for {
select {
case <-endTime:
log.Infof("[Chaos]: Time is up for the %v probe", probe.Name)
endTime = nil
for index := range chaosresult.ProbeDetails {
if chaosresult.ProbeDetails[index].Name == probe.Name {
chaosresult.ProbeDetails[index].HasProbeCompleted = true
}
}
break loop
default:
err = triggerHTTPProbe(probe, chaosresult)
// record the error inside the probeDetails, we are maintaining a dedicated variable for the err, inside probeDetails
if err != nil {
err = addProbePhase(err, string(chaosDetails.Phase))
for index := range chaosresult.ProbeDetails {
if chaosresult.ProbeDetails[index].Name == probe.Name {
chaosresult.ProbeDetails[index].IsProbeFailedWithError = err
chaosresult.ProbeDetails[index].HasProbeCompleted = true
chaosresult.ProbeDetails[index].Status.Description = getDescription(err)
isExperimentFailed = true
break loop
}
}
}
select {
case <-chaosDetails.ProbeContext.Ctx.Done():
log.Infof("Stopping %s continuous Probe", probe.Name)
for index := range chaosresult.ProbeDetails {
if chaosresult.ProbeDetails[index].Name == probe.Name {
chaosresult.ProbeDetails[index].HasProbeCompleted = true
}
}
break loop
default:
// waiting for the probe polling interval
time.Sleep(probeTimeout.ProbePollingInterval)
}
}
}
// if experiment fails and stopOnfailure is provided as true then it will patch the chaosengine for abort
// if experiment fails but stopOnfailure is provided as false then it will continue the execution
// and failed the experiment in the end
if isExperimentFailed && probe.RunProperties.StopOnFailure {
if err := stopChaosEngine(probe, clients, chaosresult, chaosDetails); err != nil {
log.Errorf("unable to patch chaosengine to stop, err: %v", err)
}
}
}
// onChaosHTTPProbe trigger the http probe for DuringChaos phase
func onChaosHTTPProbe(probe v1alpha1.ProbeAttributes, resultDetails *types.ResultDetails, clients clients.ClientSets, chaosDetails *types.ChaosDetails) {
switch probe.Mode {
case "OnChaos":
//DISPLAY THE HTTP PROBE INFO
log.InfoWithValues("[Probe]: The http probe information is as follows", logrus.Fields{
"Name": probe.Name,
"URL": probe.HTTPProbeInputs.URL,
"Run Properties": probe.RunProperties,
"Mode": probe.Mode,
"Phase": "DuringChaos",
})
go triggerOnChaosHTTPProbe(probe, clients, resultDetails, chaosDetails)
}
}