Don't export, persist or retain result body after evaluation

This commit is contained in:
TwinProduction 2021-03-08 21:30:11 -05:00
parent f945e4b8a2
commit 541e0264ab
13 changed files with 143 additions and 83 deletions

View File

@ -38,7 +38,6 @@ var (
Hostname: "example.org",
IP: "127.0.0.1",
HTTPStatus: 200,
Body: []byte("body"),
Errors: nil,
Connected: true,
Success: true,
@ -64,7 +63,6 @@ var (
Hostname: "example.org",
IP: "127.0.0.1",
HTTPStatus: 200,
Body: []byte("body"),
Errors: []string{"error-1", "error-2"},
Connected: true,
Success: false,

View File

@ -23,7 +23,7 @@ const (
// DNSRCodePlaceholder is a place holder for DNS_RCODE
//
// Values that could be NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP and REFUSED
// Values that could replace the placeholder: NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP, REFUSED
DNSRCodePlaceholder = "[DNS_RCODE]"
// ResponseTimePlaceholder is a placeholder for the request response time, in milliseconds.
@ -123,6 +123,12 @@ func (c Condition) evaluate(result *Result) bool {
return success
}
// hasBodyPlaceholder checks whether the condition has a BodyPlaceholder
// Used for determining whether the response body should be read or not
func (c Condition) hasBodyPlaceholder() bool {
return strings.Contains(string(c), BodyPlaceholder)
}
// isEqual compares two strings.
//
// Supports the pattern and the any functions.
@ -181,7 +187,7 @@ func isEqual(first, second string) bool {
func sanitizeAndResolve(elements []string, result *Result) ([]string, []string) {
parameters := make([]string, len(elements))
resolvedParameters := make([]string, len(elements))
body := strings.TrimSpace(string(result.Body))
body := strings.TrimSpace(string(result.body))
for i, element := range elements {
element = strings.TrimSpace(element)
parameters[i] = element
@ -208,7 +214,7 @@ func sanitizeAndResolve(elements []string, result *Result) ([]string, []string)
wantLength = true
element = strings.TrimSuffix(strings.TrimPrefix(element, LengthFunctionPrefix), FunctionSuffix)
}
resolvedElement, resolvedElementLength, err := jsonpath.Eval(strings.TrimPrefix(element, BodyPlaceholder+"."), result.Body)
resolvedElement, resolvedElementLength, err := jsonpath.Eval(strings.TrimPrefix(element, BodyPlaceholder+"."), result.body)
if err != nil {
if err.Error() != "unexpected end of JSON input" {
result.Errors = append(result.Errors, err.Error())

View File

@ -5,7 +5,7 @@ import "testing"
func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) {
condition := Condition("[BODY].name == any(john.doe, jane.doe)")
for n := 0; n < b.N; n++ {
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
result := &Result{body: []byte("{\"name\": \"john.doe\"}")}
condition.evaluate(result)
}
b.ReportAllocs()
@ -14,7 +14,7 @@ func BenchmarkCondition_evaluateWithBodyStringAny(b *testing.B) {
func BenchmarkCondition_evaluateWithBodyStringAnyFailure(b *testing.B) {
condition := Condition("[BODY].name == any(john.doe, jane.doe)")
for n := 0; n < b.N; n++ {
result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")}
result := &Result{body: []byte("{\"name\": \"bob.doe\"}")}
condition.evaluate(result)
}
b.ReportAllocs()
@ -23,7 +23,7 @@ func BenchmarkCondition_evaluateWithBodyStringAnyFailure(b *testing.B) {
func BenchmarkCondition_evaluateWithBodyString(b *testing.B) {
condition := Condition("[BODY].name == john.doe")
for n := 0; n < b.N; n++ {
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
result := &Result{body: []byte("{\"name\": \"john.doe\"}")}
condition.evaluate(result)
}
b.ReportAllocs()
@ -32,7 +32,7 @@ func BenchmarkCondition_evaluateWithBodyString(b *testing.B) {
func BenchmarkCondition_evaluateWithBodyStringFailure(b *testing.B) {
condition := Condition("[BODY].name == john.doe")
for n := 0; n < b.N; n++ {
result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")}
result := &Result{body: []byte("{\"name\": \"bob.doe\"}")}
condition.evaluate(result)
}
b.ReportAllocs()
@ -41,7 +41,7 @@ func BenchmarkCondition_evaluateWithBodyStringFailure(b *testing.B) {
func BenchmarkCondition_evaluateWithBodyStringLen(b *testing.B) {
condition := Condition("len([BODY].name) == 8")
for n := 0; n < b.N; n++ {
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
result := &Result{body: []byte("{\"name\": \"john.doe\"}")}
condition.evaluate(result)
}
b.ReportAllocs()
@ -50,7 +50,7 @@ func BenchmarkCondition_evaluateWithBodyStringLen(b *testing.B) {
func BenchmarkCondition_evaluateWithBodyStringLenFailure(b *testing.B) {
condition := Condition("len([BODY].name) == 8")
for n := 0; n < b.N; n++ {
result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")}
result := &Result{body: []byte("{\"name\": \"bob.doe\"}")}
condition.evaluate(result)
}
b.ReportAllocs()

View File

@ -162,7 +162,7 @@ func TestCondition_evaluateWithResponseTimeUsingLessThanOrEqualTo(t *testing.T)
func TestCondition_evaluateWithBody(t *testing.T) {
condition := Condition("[BODY] == test")
result := &Result{Body: []byte("test")}
result := &Result{body: []byte("test")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -175,7 +175,7 @@ func TestCondition_evaluateWithBody(t *testing.T) {
func TestCondition_evaluateWithBodyJSONPath(t *testing.T) {
condition := Condition("[BODY].status == UP")
result := &Result{Body: []byte("{\"status\":\"UP\"}")}
result := &Result{body: []byte("{\"status\":\"UP\"}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -188,7 +188,7 @@ func TestCondition_evaluateWithBodyJSONPath(t *testing.T) {
func TestCondition_evaluateWithBodyJSONPathComplex(t *testing.T) {
condition := Condition("[BODY].data.name == john")
result := &Result{Body: []byte("{\"data\": {\"id\": 1, \"name\": \"john\"}}")}
result := &Result{body: []byte("{\"data\": {\"id\": 1, \"name\": \"john\"}}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -201,7 +201,7 @@ func TestCondition_evaluateWithBodyJSONPathComplex(t *testing.T) {
func TestCondition_evaluateWithInvalidBodyJSONPathComplex(t *testing.T) {
condition := Condition("[BODY].data.name == john")
result := &Result{Body: []byte("{\"data\": {\"id\": 1}}")}
result := &Result{body: []byte("{\"data\": {\"id\": 1}}")}
condition.evaluate(result)
if result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a failure, because the path was invalid", condition)
@ -214,7 +214,7 @@ func TestCondition_evaluateWithInvalidBodyJSONPathComplex(t *testing.T) {
func TestCondition_evaluateWithInvalidBodyJSONPathComplexWithLengthFunction(t *testing.T) {
condition := Condition("len([BODY].data.name) == john")
result := &Result{Body: []byte("{\"data\": {\"id\": 1}}")}
result := &Result{body: []byte("{\"data\": {\"id\": 1}}")}
condition.evaluate(result)
if result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a failure, because the path was invalid", condition)
@ -227,7 +227,7 @@ func TestCondition_evaluateWithInvalidBodyJSONPathComplexWithLengthFunction(t *t
func TestCondition_evaluateWithBodyJSONPathDoublePlaceholders(t *testing.T) {
condition := Condition("[BODY].user.firstName != [BODY].user.lastName")
result := &Result{Body: []byte("{\"user\": {\"firstName\": \"john\", \"lastName\": \"doe\"}}")}
result := &Result{body: []byte("{\"user\": {\"firstName\": \"john\", \"lastName\": \"doe\"}}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -240,7 +240,7 @@ func TestCondition_evaluateWithBodyJSONPathDoublePlaceholders(t *testing.T) {
func TestCondition_evaluateWithBodyJSONPathDoublePlaceholdersFailure(t *testing.T) {
condition := Condition("[BODY].user.firstName == [BODY].user.lastName")
result := &Result{Body: []byte("{\"user\": {\"firstName\": \"john\", \"lastName\": \"doe\"}}")}
result := &Result{body: []byte("{\"user\": {\"firstName\": \"john\", \"lastName\": \"doe\"}}")}
condition.evaluate(result)
if result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a failure", condition)
@ -253,7 +253,7 @@ func TestCondition_evaluateWithBodyJSONPathDoublePlaceholdersFailure(t *testing.
func TestCondition_evaluateWithBodyJSONPathLongInt(t *testing.T) {
condition := Condition("[BODY].data.id == 1")
result := &Result{Body: []byte("{\"data\": {\"id\": 1}}")}
result := &Result{body: []byte("{\"data\": {\"id\": 1}}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -266,7 +266,7 @@ func TestCondition_evaluateWithBodyJSONPathLongInt(t *testing.T) {
func TestCondition_evaluateWithBodyJSONPathComplexInt(t *testing.T) {
condition := Condition("[BODY].data[1].id == 2")
result := &Result{Body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}
result := &Result{body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -279,7 +279,7 @@ func TestCondition_evaluateWithBodyJSONPathComplexInt(t *testing.T) {
func TestCondition_evaluateWithBodyJSONPathComplexIntUsingGreaterThan(t *testing.T) {
condition := Condition("[BODY].data.id > 0")
result := &Result{Body: []byte("{\"data\": {\"id\": 1}}")}
result := &Result{body: []byte("{\"data\": {\"id\": 1}}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -292,7 +292,7 @@ func TestCondition_evaluateWithBodyJSONPathComplexIntUsingGreaterThan(t *testing
func TestCondition_evaluateWithBodyJSONPathComplexIntFailureUsingGreaterThan(t *testing.T) {
condition := Condition("[BODY].data.id > 5")
result := &Result{Body: []byte("{\"data\": {\"id\": 1}}")}
result := &Result{body: []byte("{\"data\": {\"id\": 1}}")}
condition.evaluate(result)
if result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a failure", condition)
@ -305,7 +305,7 @@ func TestCondition_evaluateWithBodyJSONPathComplexIntFailureUsingGreaterThan(t *
func TestCondition_evaluateWithBodyJSONPathComplexIntUsingLessThan(t *testing.T) {
condition := Condition("[BODY].data.id < 5")
result := &Result{Body: []byte("{\"data\": {\"id\": 2}}")}
result := &Result{body: []byte("{\"data\": {\"id\": 2}}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -318,7 +318,7 @@ func TestCondition_evaluateWithBodyJSONPathComplexIntUsingLessThan(t *testing.T)
func TestCondition_evaluateWithBodyJSONPathComplexIntFailureUsingLessThan(t *testing.T) {
condition := Condition("[BODY].data.id < 5")
result := &Result{Body: []byte("{\"data\": {\"id\": 10}}")}
result := &Result{body: []byte("{\"data\": {\"id\": 10}}")}
condition.evaluate(result)
if result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a failure", condition)
@ -331,7 +331,7 @@ func TestCondition_evaluateWithBodyJSONPathComplexIntFailureUsingLessThan(t *tes
func TestCondition_evaluateWithBodySliceLength(t *testing.T) {
condition := Condition("len([BODY].data) == 3")
result := &Result{Body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}
result := &Result{body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -344,7 +344,7 @@ func TestCondition_evaluateWithBodySliceLength(t *testing.T) {
func TestCondition_evaluateWithBodyStringLength(t *testing.T) {
condition := Condition("len([BODY].name) == 8")
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
result := &Result{body: []byte("{\"name\": \"john.doe\"}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -357,7 +357,7 @@ func TestCondition_evaluateWithBodyStringLength(t *testing.T) {
func TestCondition_evaluateWithBodyPattern(t *testing.T) {
condition := Condition("[BODY] == pat(*john*)")
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
result := &Result{body: []byte("{\"name\": \"john.doe\"}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -370,7 +370,7 @@ func TestCondition_evaluateWithBodyPattern(t *testing.T) {
func TestCondition_evaluateWithReverseBodyPattern(t *testing.T) {
condition := Condition("pat(*john*) == [BODY]")
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
result := &Result{body: []byte("{\"name\": \"john.doe\"}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -383,7 +383,7 @@ func TestCondition_evaluateWithReverseBodyPattern(t *testing.T) {
func TestCondition_evaluateWithBodyStringPattern(t *testing.T) {
condition := Condition("[BODY].name == pat(*ohn*)")
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
result := &Result{body: []byte("{\"name\": \"john.doe\"}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -397,7 +397,7 @@ func TestCondition_evaluateWithBodyStringPattern(t *testing.T) {
func TestCondition_evaluateWithBodyHTMLPattern(t *testing.T) {
var html = `<!DOCTYPE html><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body><div id="user">john.doe</div></body></html>`
condition := Condition("[BODY] == pat(*<div id=\"user\">john.doe</div>*)")
result := &Result{Body: []byte(html)}
result := &Result{body: []byte(html)}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
@ -410,7 +410,7 @@ func TestCondition_evaluateWithBodyHTMLPattern(t *testing.T) {
func TestCondition_evaluateWithBodyStringPatternFailure(t *testing.T) {
condition := Condition("[BODY].name == pat(bob*)")
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
result := &Result{body: []byte("{\"name\": \"john.doe\"}")}
condition.evaluate(result)
if result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a failure", condition)
@ -477,8 +477,8 @@ func TestCondition_evaluateWithBodyStringAny(t *testing.T) {
condition := Condition("[BODY].name == any(john.doe, jane.doe)")
expectedConditionDisplayed := "[BODY].name == any(john.doe, jane.doe)"
results := []*Result{
{Body: []byte("{\"name\": \"john.doe\"}")},
{Body: []byte("{\"name\": \"jane.doe\"}")},
{body: []byte("{\"name\": \"john.doe\"}")},
{body: []byte("{\"name\": \"jane.doe\"}")},
}
for _, result := range results {
success := condition.evaluate(result)
@ -493,7 +493,7 @@ func TestCondition_evaluateWithBodyStringAny(t *testing.T) {
func TestCondition_evaluateWithBodyStringAnyFailure(t *testing.T) {
condition := Condition("[BODY].name == any(john.doe, jane.doe)")
result := &Result{Body: []byte("{\"name\": \"bob.doe\"}")}
result := &Result{body: []byte("{\"name\": \"bob.doe\"}")}
condition.evaluate(result)
if result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a failure", condition)
@ -660,3 +660,27 @@ func TestCondition_evaluateWithCertificateExpirationGreaterThanDurationFailure(t
t.Errorf("Condition '%s' should have resolved to '%s', got '%s'", condition, expectedConditionDisplayed, result.ConditionResults[0].Condition)
}
}
func TestCondition_evaluateWithInvalidOperator(t *testing.T) {
condition := Condition("[STATUS] ? 201")
result := &Result{HTTPStatus: 201}
condition.evaluate(result)
if result.Success {
t.Error("condition was invalid, result should've been a failure")
}
if len(result.Errors) != 1 {
t.Error("condition was invalid, result should've had an error")
}
}
func TestCondition_evaluateWithNoPlaceholder(t *testing.T) {
condition := Condition("1 == 2")
result := &Result{}
condition.evaluate(result)
if result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a failure", condition)
}
if result.ConditionResults[0].Condition != string(condition) {
t.Errorf("Condition '%s' should have resolved to '%s', got '%s'", condition, condition, result.ConditionResults[0].Condition)
}
}

View File

@ -60,26 +60,26 @@ func (d *DNS) query(url string, result *Result) {
switch rr.Header().Rrtype {
case dns.TypeA:
if a, ok := rr.(*dns.A); ok {
result.Body = []byte(a.A.String())
result.body = []byte(a.A.String())
}
case dns.TypeAAAA:
if aaaa, ok := rr.(*dns.AAAA); ok {
result.Body = []byte(aaaa.AAAA.String())
result.body = []byte(aaaa.AAAA.String())
}
case dns.TypeCNAME:
if cname, ok := rr.(*dns.CNAME); ok {
result.Body = []byte(cname.Target)
result.body = []byte(cname.Target)
}
case dns.TypeMX:
if mx, ok := rr.(*dns.MX); ok {
result.Body = []byte(mx.Mx)
result.body = []byte(mx.Mx)
}
case dns.TypeNS:
if ns, ok := rr.(*dns.NS); ok {
result.Body = []byte(ns.Ns)
result.body = []byte(ns.Ns)
}
default:
result.Body = []byte("query type is not supported yet")
result.body = []byte("query type is not supported yet")
}
}
}

View File

@ -91,12 +91,12 @@ func TestIntegrationQuery(t *testing.T) {
if test.inputDNS.QueryType == "NS" {
// Because there are often multiple nameservers backing a single domain, we'll only look at the suffix
if !pattern.Match(test.expectedBody, string(result.Body)) {
t.Errorf("got %s, expected result %s,", string(result.Body), test.expectedBody)
if !pattern.Match(test.expectedBody, string(result.body)) {
t.Errorf("got %s, expected result %s,", string(result.body), test.expectedBody)
}
} else {
if string(result.Body) != test.expectedBody {
t.Errorf("got %s, expected result %s,", string(result.Body), test.expectedBody)
if string(result.body) != test.expectedBody {
t.Errorf("got %s, expected result %s,", string(result.body), test.expectedBody)
}
}
})

View File

@ -12,10 +12,7 @@ type Result struct {
// DNSRCode is the response code of a DNS query in a human readable format
DNSRCode string `json:"-"`
// Body is the response body
Body []byte `json:"-"`
// Hostname extracted from the Service URL
// Hostname extracted from Service.URL
Hostname string `json:"hostname"`
// IP resolved from the Service URL
@ -41,4 +38,11 @@ type Result struct {
// CertificateExpiration is the duration before the certificate expires
CertificateExpiration time.Duration `json:"-"`
// body is the response body
//
// Note that this variable is only used during the evaluation of a service's health.
// This means that the call Service.EvaluateHealth both populates the body (if necessary)
// and sets it to nil after the evaluation has been completed.
body []byte
}

View File

@ -29,7 +29,7 @@ var (
Hostname: "example.org",
IP: "127.0.0.1",
HTTPStatus: 200,
Body: []byte("body"),
body: []byte("body"),
Errors: nil,
Connected: true,
Success: true,
@ -55,7 +55,7 @@ var (
Hostname: "example.org",
IP: "127.0.0.1",
HTTPStatus: 200,
Body: []byte("body"),
body: []byte("body"),
Errors: []string{"error-1", "error-2"},
Connected: true,
Success: false,

View File

@ -80,7 +80,7 @@ type Service struct {
// NumberOfFailuresInARow is the number of unsuccessful evaluations in a row
NumberOfFailuresInARow int
// NumberOfFailuresInARow is the number of successful evaluations in a row
// NumberOfSuccessesInARow is the number of successful evaluations in a row
NumberOfSuccessesInARow int
}
@ -149,6 +149,8 @@ func (service *Service) EvaluateHealth() *Result {
}
}
result.Timestamp = time.Now()
// No need to keep the body after the service has been evaluated
result.body = nil
return result
}
@ -220,9 +222,12 @@ func (service *Service) call(result *Result) {
}
result.HTTPStatus = response.StatusCode
result.Connected = response.StatusCode > 0
result.Body, err = ioutil.ReadAll(response.Body)
if err != nil {
result.Errors = append(result.Errors, err.Error())
// Only read the body if there's a condition that uses the BodyPlaceholder
if service.needsToReadBody() {
result.body, err = ioutil.ReadAll(response.Body)
if err != nil {
result.Errors = append(result.Errors, err.Error())
}
}
}
}
@ -247,3 +252,13 @@ func (service *Service) buildHTTPRequest() *http.Request {
}
return request
}
// needsToReadBody checks if there's any conditions that requires the response body to be read
func (service *Service) needsToReadBody() bool {
for _, condition := range service.Conditions {
if condition.hasBodyPlaceholder() {
return true
}
}
return false
}

View File

@ -10,7 +10,7 @@ import (
func TestService_ValidateAndSetDefaults(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "TwiNNatioN",
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Conditions: []*Condition{&condition},
Alerts: []*Alert{{Type: PagerDutyAlert}},
@ -94,7 +94,7 @@ func TestService_ValidateAndSetDefaultsWithDNS(t *testing.T) {
func TestService_GetAlertsTriggered(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "TwiNNatioN",
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Conditions: []*Condition{&condition},
Alerts: []*Alert{{Type: PagerDutyAlert, Enabled: true}},
@ -118,7 +118,7 @@ func TestService_GetAlertsTriggered(t *testing.T) {
func TestService_buildHTTPRequest(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "TwiNNatioN",
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Conditions: []*Condition{&condition},
}
@ -138,7 +138,7 @@ func TestService_buildHTTPRequest(t *testing.T) {
func TestService_buildHTTPRequestWithCustomUserAgent(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "TwiNNatioN",
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Conditions: []*Condition{&condition},
Headers: map[string]string{
@ -161,7 +161,7 @@ func TestService_buildHTTPRequestWithCustomUserAgent(t *testing.T) {
func TestService_buildHTTPRequestWithHostHeader(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "TwiNNatioN",
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Method: "POST",
Conditions: []*Condition{&condition},
@ -182,7 +182,7 @@ func TestService_buildHTTPRequestWithHostHeader(t *testing.T) {
func TestService_buildHTTPRequestWithGraphQLEnabled(t *testing.T) {
condition := Condition("[STATUS] == 200")
service := Service{
Name: "TwiNNatioN",
Name: "twinnation-graphql",
URL: "https://twinnation.org/graphql",
Method: "POST",
Conditions: []*Condition{&condition},
@ -206,16 +206,17 @@ func TestService_buildHTTPRequestWithGraphQLEnabled(t *testing.T) {
}
body, _ := ioutil.ReadAll(request.Body)
if !strings.HasPrefix(string(body), "{\"query\":") {
t.Error("request.Body should've started with '{\"query\":', but it didn't:", string(body))
t.Error("request.body should've started with '{\"query\":', but it didn't:", string(body))
}
}
func TestIntegrationEvaluateHealth(t *testing.T) {
condition := Condition("[STATUS] == 200")
bodyCondition := Condition("[BODY].status == UP")
service := Service{
Name: "TwiNNatioN",
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Conditions: []*Condition{&condition},
Conditions: []*Condition{&condition, &bodyCondition},
}
result := service.EvaluateHealth()
if !result.ConditionResults[0].Success {
@ -232,7 +233,7 @@ func TestIntegrationEvaluateHealth(t *testing.T) {
func TestIntegrationEvaluateHealthWithFailure(t *testing.T) {
condition := Condition("[STATUS] == 500")
service := Service{
Name: "TwiNNatioN",
Name: "twinnation-health",
URL: "https://twinnation.org/health",
Conditions: []*Condition{&condition},
}
@ -252,7 +253,7 @@ func TestIntegrationEvaluateHealthForDNS(t *testing.T) {
conditionSuccess := Condition("[DNS_RCODE] == NOERROR")
conditionBody := Condition("[BODY] == 93.184.216.34")
service := Service{
Name: "TwiNNatioN",
Name: "example",
URL: "8.8.8.8",
DNS: &DNS{
QueryType: "A",
@ -275,7 +276,7 @@ func TestIntegrationEvaluateHealthForDNS(t *testing.T) {
func TestIntegrationEvaluateHealthForICMP(t *testing.T) {
conditionSuccess := Condition("[CONNECTED] == true")
service := Service{
Name: "ICMP test",
Name: "icmp-test",
URL: "icmp://127.0.0.1",
Conditions: []*Condition{&conditionSuccess},
}
@ -294,7 +295,7 @@ func TestIntegrationEvaluateHealthForICMP(t *testing.T) {
func TestService_getIP(t *testing.T) {
conditionSuccess := Condition("[CONNECTED] == true")
service := Service{
Name: "Invalid URL test",
Name: "invalid-url-test",
URL: "",
Conditions: []*Condition{&conditionSuccess},
}
@ -304,3 +305,27 @@ func TestService_getIP(t *testing.T) {
t.Error("service.getIP(result) should've thrown an error because the URL is invalid, thus cannot be parsed")
}
}
func TestService_NeedsToReadBody(t *testing.T) {
statusCondition := Condition("[STATUS] == 200")
bodyCondition := Condition("[BODY].status == UP")
bodyConditionWithLength := Condition("len([BODY].tags) > 0")
if (&Service{Conditions: []*Condition{&statusCondition}}).needsToReadBody() {
t.Error("expected false, got true")
}
if !(&Service{Conditions: []*Condition{&bodyCondition}}).needsToReadBody() {
t.Error("expected true, got false")
}
if !(&Service{Conditions: []*Condition{&bodyConditionWithLength}}).needsToReadBody() {
t.Error("expected true, got false")
}
if !(&Service{Conditions: []*Condition{&statusCondition, &bodyCondition}}).needsToReadBody() {
t.Error("expected true, got false")
}
if !(&Service{Conditions: []*Condition{&bodyCondition, &statusCondition}}).needsToReadBody() {
t.Error("expected true, got false")
}
if !(&Service{Conditions: []*Condition{&bodyConditionWithLength, &statusCondition}}).needsToReadBody() {
t.Error("expected true, got false")
}
}

View File

@ -33,7 +33,6 @@ var (
Hostname: "example.org",
IP: "127.0.0.1",
HTTPStatus: 200,
Body: []byte("body"),
Errors: nil,
Connected: true,
Success: true,
@ -59,7 +58,6 @@ var (
Hostname: "example.org",
IP: "127.0.0.1",
HTTPStatus: 200,
Body: []byte("body"),
Errors: []string{"error-1", "error-2"},
Connected: true,
Success: false,
@ -107,9 +105,6 @@ func TestStore_Insert(t *testing.T) {
if r.DNSRCode != expectedResult.DNSRCode {
t.Errorf("Result at index %d should've had a DNSRCode of %s, but was actually %s", i, expectedResult.DNSRCode, r.DNSRCode)
}
if len(r.Body) != len(expectedResult.Body) {
t.Errorf("Result at index %d should've had a body of length %d, but was actually %d", i, len(expectedResult.Body), len(r.Body))
}
if r.Hostname != expectedResult.Hostname {
t.Errorf("Result at index %d should've had a Hostname of %s, but was actually %s", i, expectedResult.Hostname, r.Hostname)
}

View File

@ -32,7 +32,6 @@ var (
Hostname: "example.org",
IP: "127.0.0.1",
HTTPStatus: 200,
Body: []byte("body"),
Errors: nil,
Connected: true,
Success: true,
@ -58,7 +57,6 @@ var (
Hostname: "example.org",
IP: "127.0.0.1",
HTTPStatus: 200,
Body: []byte("body"),
Errors: []string{"error-1", "error-2"},
Connected: true,
Success: false,

View File

@ -1,7 +1,6 @@
package watchdog
import (
"fmt"
"log"
"sync"
"time"
@ -37,26 +36,22 @@ func monitor(service *core.Service) {
monitoringMutex.Lock()
}
if cfg.Debug {
log.Printf("[watchdog][monitor] Monitoring serviceName=%s", service.Name)
log.Printf("[watchdog][monitor] Monitoring group=%s; service=%s", service.Group, service.Name)
}
result := service.EvaluateHealth()
metric.PublishMetricsForService(service, result)
UpdateServiceStatuses(service, result)
var extra string
if !result.Success {
extra = fmt.Sprintf("responseBody=%s", result.Body)
}
log.Printf(
"[watchdog][monitor] Monitored serviceName=%s; success=%v; errors=%d; requestDuration=%s; %s",
"[watchdog][monitor] Monitored group=%s; service=%s; success=%v; errors=%d; duration=%s",
service.Group,
service.Name,
result.Success,
len(result.Errors),
result.Duration.Round(time.Millisecond),
extra,
)
HandleAlerting(service, result)
if cfg.Debug {
log.Printf("[watchdog][monitor] Waiting for interval=%s before monitoring serviceName=%s again", service.Interval, service.Name)
log.Printf("[watchdog][monitor] Waiting for interval=%s before monitoring group=%s service=%s again", service.Interval, service.Group, service.Name)
}
if !cfg.DisableMonitoringLock {
monitoringMutex.Unlock()