-
Notifications
You must be signed in to change notification settings - Fork 67
/
polling_nrql_validator.go
114 lines (92 loc) · 3.04 KB
/
polling_nrql_validator.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
package validation
import (
"context"
"errors"
"fmt"
"strings"
"time"
configAPI "github.com/newrelic/newrelic-cli/internal/config/api"
"github.com/newrelic/newrelic-cli/internal/utils"
"github.com/newrelic/newrelic-client-go/pkg/nrdb"
)
const (
DefaultMaxAttempts = 60
DefaultIntervalSeconds = 5
ReachedMaxValidationMsg = "reached max validation attempts"
)
// PollingNRQLValidator polls NRDB to assert data is being reported for the given query.
type PollingNRQLValidator struct {
MaxAttempts int
IntervalMilliSeconds int
client utils.NRDBClient
}
// NewPollingNRQLValidator returns a new instance of PollingNRQLValidator.
func NewPollingNRQLValidator(c utils.NRDBClient) *PollingNRQLValidator {
v := PollingNRQLValidator{
client: c,
MaxAttempts: DefaultMaxAttempts,
IntervalMilliSeconds: DefaultIntervalSeconds * 1000,
}
return &v
}
// Validate polls NRDB to assert data is being reported for the given query.
func (m *PollingNRQLValidator) Validate(ctx context.Context, query string) (string, error) {
ticker := time.NewTicker(time.Duration(m.IntervalMilliSeconds) * time.Millisecond)
defer ticker.Stop()
entityGUID, err := m.tryValidate(ctx, query)
if err != nil {
if strings.Contains(err.Error(), "context canceled") {
return "", err
}
return "", fmt.Errorf("%s: %s", ReachedMaxValidationMsg, err)
}
return entityGUID, nil
}
func (m *PollingNRQLValidator) tryValidate(ctx context.Context, query string) (string, error) {
guid := ""
validatorFunc := func() error {
results, err := m.executeQuery(ctx, query)
if err != nil {
return err
}
if len(results) == 0 {
return errors.New("no results returned")
}
// The query is assumed to use a count aggregate function
count := results[0]["count"].(float64)
if count > 0 {
// Try and parse an entity GUID from the results. The query is assumed to
// optionally use a facet over entityGuid. The standard case seems to be
// that all entities contain a facet of "entityGuid", and so if we find it
// here, we return it.
if entityGUID, ok := results[0]["entityGuid"]; ok {
guid = entityGUID.(string)
return nil
}
// In the logs integration, the facet doesn't contain "entityGuid", but
// does contain, "entity.guid", so here we check for that also.
if entityGUID, ok := results[0]["entity.guids"]; ok {
guid = entityGUID.(string)
return nil
}
// entity guid is optional, no error returned
return nil
}
return errors.New("no count found in results")
}
r := utils.NewRetry(m.MaxAttempts, m.IntervalMilliSeconds, validatorFunc)
retryCtx := r.ExecWithRetries(ctx)
if !retryCtx.Success {
return "", retryCtx.MostRecentError()
}
return guid, nil
}
func (m *PollingNRQLValidator) executeQuery(ctx context.Context, query string) ([]nrdb.NRDBResult, error) {
accountID := configAPI.RequireActiveProfileAccountID()
nrql := nrdb.NRQL(query)
result, err := m.client.QueryWithContext(ctx, accountID, nrql)
if err != nil {
return nil, err
}
return result.Results, nil
}