forked from hashicorp/nomad
/
prometheus.go
147 lines (130 loc) · 3.97 KB
/
prometheus.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
package metrics
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"github.com/hashicorp/nomad/e2e/e2eutil"
"github.com/hashicorp/nomad/e2e/framework"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/prometheus/common/model"
)
func (tc *MetricsTest) setUpPrometheus(f *framework.F) error {
uuid := uuid.Generate()
fabioID := "fabio" + uuid[0:8]
fabioAllocs := e2eutil.RegisterAndWaitForAllocs(f.T(), tc.Nomad(),
"fabio/fabio.nomad", fabioID, "")
if len(fabioAllocs) < 1 {
return fmt.Errorf("fabio failed to start")
}
tc.fabioID = fabioID
// get a fabio IP address so we can query it later
nodeDetails, _, err := tc.Nomad().Nodes().Info(fabioAllocs[0].NodeID, nil)
if err != nil {
return err
}
// TODO(tgross): currently this forces us to run the target on AWS rather
// than any other environment. There's a Provider environment in the E2E
// framework we're not currently using; we should revisit that.
publicIP := nodeDetails.Attributes["unique.platform.aws.public-ipv4"]
tc.fabioAddress = fmt.Sprintf("http://%s:9999", publicIP)
prometheusID := "prometheus" + uuid[0:8]
prometheusAllocs := e2eutil.RegisterAndWaitForAllocs(f.T(), tc.Nomad(),
"prometheus/prometheus.nomad", prometheusID, "")
if len(prometheusAllocs) < 1 {
return fmt.Errorf("prometheus failed to start")
}
tc.prometheusID = prometheusID
return nil
}
func (tc *MetricsTest) tearDownPrometheus(f *framework.F) {
tc.Nomad().Jobs().Deregister(tc.prometheusID, true, nil)
tc.Nomad().Jobs().Deregister(tc.fabioID, true, nil)
tc.Nomad().System().GarbageCollect()
}
// "Wait, why aren't we just using the prometheus golang client?", you ask?
// Nomad has vendored an older version of the prometheus exporter library
// their HTTP client which only works with a newer version is also is marked
// "alpha", and there's API v2 work currently ongoing. Rather than waiting
// till 0.11 to ship this test, this just handles the query API and can be
// swapped out later.
//
// TODO(tgross) / COMPAT(0.11): update our prometheus libraries
func (tc *MetricsTest) promQuery(query string) (model.Vector, error) {
var err error
promUrl := tc.fabioAddress + "/api/v1/query"
formValues := url.Values{"query": {query}}
resp, err := http.PostForm(promUrl, formValues)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP status: %v", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
apiResp := &apiResponse{}
err = json.Unmarshal(body, apiResp)
if err != nil {
return nil, err
}
if apiResp.Status == "error" {
return nil, fmt.Errorf("API error: %v: %v", apiResp.ErrorType, apiResp.Error)
}
// unpack query
var qs queryResult
err = json.Unmarshal(apiResp.Data, &qs)
if err != nil {
return nil, err
}
val, ok := qs.v.(model.Vector)
if !ok || len(val) == 0 {
return nil, fmt.Errorf("no metrics data available")
}
return val, nil
}
type apiResponse struct {
Status string `json:"status"`
Data json.RawMessage `json:"data"`
ErrorType string `json:"errorType"`
Error string `json:"error"`
Warnings []string `json:"warnings,omitempty"`
}
// queryResult contains result data for a query.
type queryResult struct {
Type model.ValueType `json:"resultType"`
Result interface{} `json:"result"`
// The decoded value.
v model.Value
}
func (qr *queryResult) UnmarshalJSON(b []byte) error {
v := struct {
Type model.ValueType `json:"resultType"`
Result json.RawMessage `json:"result"`
}{}
err := json.Unmarshal(b, &v)
if err != nil {
return err
}
switch v.Type {
case model.ValScalar:
var sv model.Scalar
err = json.Unmarshal(v.Result, &sv)
qr.v = &sv
case model.ValVector:
var vv model.Vector
err = json.Unmarshal(v.Result, &vv)
qr.v = vv
case model.ValMatrix:
var mv model.Matrix
err = json.Unmarshal(v.Result, &mv)
qr.v = mv
default:
err = fmt.Errorf("no metrics data available")
}
return err
}