github.com/hernad/nomad@v1.6.112/e2e/metrics/prometheus.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package metrics 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "io" 10 "net/http" 11 "net/url" 12 13 "github.com/hernad/nomad/e2e/e2eutil" 14 "github.com/hernad/nomad/e2e/framework" 15 "github.com/hernad/nomad/helper/uuid" 16 "github.com/prometheus/common/model" 17 ) 18 19 func (tc *MetricsTest) setUpPrometheus(f *framework.F) error { 20 uuid := uuid.Generate() 21 fabioID := "fabio" + uuid[0:8] 22 fabioAllocs := e2eutil.RegisterAndWaitForAllocs(f.T(), tc.Nomad(), 23 "metrics/input/fabio.nomad", fabioID, "") 24 if len(fabioAllocs) < 1 { 25 return fmt.Errorf("fabio failed to start") 26 } 27 tc.fabioID = fabioID 28 29 // get a fabio IP address so we can query it later 30 nodeDetails, _, err := tc.Nomad().Nodes().Info(fabioAllocs[0].NodeID, nil) 31 if err != nil { 32 return err 33 } 34 35 // TODO(tgross): currently this forces us to run the target on AWS rather 36 // than any other environment. There's a Provider environment in the E2E 37 // framework we're not currently using; we should revisit that. 38 publicIP := nodeDetails.Attributes["unique.platform.aws.public-ipv4"] 39 tc.fabioAddress = fmt.Sprintf("http://%s:9999", publicIP) 40 prometheusID := "prometheus" + uuid[0:8] 41 prometheusAllocs := e2eutil.RegisterAndWaitForAllocs(f.T(), tc.Nomad(), 42 "metrics/input/prometheus.nomad", prometheusID, "") 43 if len(prometheusAllocs) < 1 { 44 return fmt.Errorf("prometheus failed to start") 45 } 46 tc.prometheusID = prometheusID 47 return nil 48 } 49 50 func (tc *MetricsTest) tearDownPrometheus(f *framework.F) { 51 tc.Nomad().Jobs().Deregister(tc.prometheusID, true, nil) 52 tc.Nomad().Jobs().Deregister(tc.fabioID, true, nil) 53 tc.Nomad().System().GarbageCollect() 54 } 55 56 // "Wait, why aren't we just using the prometheus golang client?", you ask? 57 // Nomad has vendored an older version of the prometheus exporter library 58 // their HTTP client which only works with a newer version is also is marked 59 // "alpha", and there's API v2 work currently ongoing. Rather than waiting 60 // till 0.11 to ship this test, this just handles the query API and can be 61 // swapped out later. 62 // 63 // TODO(tgross) / COMPAT(0.11): update our prometheus libraries 64 func (tc *MetricsTest) promQuery(query string) (model.Vector, error) { 65 var err error 66 promUrl := tc.fabioAddress + "/api/v1/query" 67 formValues := url.Values{"query": {query}} 68 resp, err := http.PostForm(promUrl, formValues) 69 if err != nil { 70 return nil, err 71 } 72 defer resp.Body.Close() 73 if resp.StatusCode != http.StatusOK { 74 return nil, fmt.Errorf("HTTP status: %v", resp.StatusCode) 75 } 76 body, err := io.ReadAll(resp.Body) 77 if err != nil { 78 return nil, err 79 } 80 apiResp := &apiResponse{} 81 err = json.Unmarshal(body, apiResp) 82 if err != nil { 83 return nil, err 84 } 85 if apiResp.Status == "error" { 86 return nil, fmt.Errorf("API error: %v: %v", apiResp.ErrorType, apiResp.Error) 87 } 88 89 // unpack query 90 var qs queryResult 91 err = json.Unmarshal(apiResp.Data, &qs) 92 if err != nil { 93 return nil, err 94 } 95 val, ok := qs.v.(model.Vector) 96 if !ok || len(val) == 0 { 97 return nil, fmt.Errorf("no metrics data available") 98 } 99 return val, nil 100 } 101 102 type apiResponse struct { 103 Status string `json:"status"` 104 Data json.RawMessage `json:"data"` 105 ErrorType string `json:"errorType"` 106 Error string `json:"error"` 107 Warnings []string `json:"warnings,omitempty"` 108 } 109 110 // queryResult contains result data for a query. 111 type queryResult struct { 112 Type model.ValueType `json:"resultType"` 113 Result interface{} `json:"result"` 114 115 // The decoded value. 116 v model.Value 117 } 118 119 func (qr *queryResult) UnmarshalJSON(b []byte) error { 120 v := struct { 121 Type model.ValueType `json:"resultType"` 122 Result json.RawMessage `json:"result"` 123 }{} 124 125 err := json.Unmarshal(b, &v) 126 if err != nil { 127 return err 128 } 129 130 switch v.Type { 131 case model.ValScalar: 132 var sv model.Scalar 133 err = json.Unmarshal(v.Result, &sv) 134 qr.v = &sv 135 136 case model.ValVector: 137 var vv model.Vector 138 err = json.Unmarshal(v.Result, &vv) 139 qr.v = vv 140 141 case model.ValMatrix: 142 var mv model.Matrix 143 err = json.Unmarshal(v.Result, &mv) 144 qr.v = mv 145 146 default: 147 err = fmt.Errorf("no metrics data available") 148 } 149 return err 150 }