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  }