github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/e2e/metrics/prometheus.go (about)

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