github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/integration/resources/coordinator_client.go (about)

     1  // Copyright (c) 2021  Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package resources
    22  
    23  import (
    24  	"bytes"
    25  	"context"
    26  	"encoding/json"
    27  	"errors"
    28  	"fmt"
    29  	"io/ioutil"
    30  	"net"
    31  	"net/http"
    32  	"path"
    33  	"strings"
    34  	"time"
    35  
    36  	"github.com/m3db/m3/src/cluster/generated/proto/placementpb"
    37  	"github.com/m3db/m3/src/cluster/placementhandler"
    38  	"github.com/m3db/m3/src/query/api/v1/handler/graphite"
    39  	"github.com/m3db/m3/src/query/api/v1/handler/prometheus/native"
    40  	"github.com/m3db/m3/src/query/api/v1/handler/topic"
    41  	"github.com/m3db/m3/src/query/api/v1/options"
    42  	"github.com/m3db/m3/src/query/api/v1/route"
    43  	"github.com/m3db/m3/src/query/generated/proto/admin"
    44  	"github.com/m3db/m3/src/query/generated/proto/prompb"
    45  	"github.com/m3db/m3/src/x/headers"
    46  	xhttp "github.com/m3db/m3/src/x/net/http"
    47  
    48  	"github.com/gogo/protobuf/jsonpb"
    49  	"github.com/gogo/protobuf/proto"
    50  	"github.com/golang/snappy"
    51  	"github.com/prometheus/common/model"
    52  	"go.uber.org/zap"
    53  	"go.uber.org/zap/zapcore"
    54  )
    55  
    56  var errUnknownServiceType = errors.New("unknown service type")
    57  
    58  // RetryFunc is a function that retries the provided
    59  // operation until successful.
    60  type RetryFunc func(op func() error) error
    61  
    62  // zapMethod appends the method as a log field.
    63  func zapMethod(s string) zapcore.Field { return zap.String("method", s) }
    64  
    65  // CoordinatorClient is a client use to invoke API calls
    66  // on a coordinator
    67  type CoordinatorClient struct {
    68  	client    *http.Client
    69  	httpPort  int
    70  	logger    *zap.Logger
    71  	retryFunc RetryFunc
    72  }
    73  
    74  // CoordinatorClientOptions are the options for the CoordinatorClient.
    75  type CoordinatorClientOptions struct {
    76  	Client    *http.Client
    77  	HTTPPort  int
    78  	Logger    *zap.Logger
    79  	RetryFunc RetryFunc
    80  }
    81  
    82  // NewCoordinatorClient creates a new CoordinatorClient.
    83  func NewCoordinatorClient(opts CoordinatorClientOptions) CoordinatorClient {
    84  	return CoordinatorClient{
    85  		client:    opts.Client,
    86  		httpPort:  opts.HTTPPort,
    87  		logger:    opts.Logger,
    88  		retryFunc: opts.RetryFunc,
    89  	}
    90  }
    91  
    92  func (c *CoordinatorClient) makeURL(resource string) string {
    93  	return fmt.Sprintf("http://0.0.0.0:%d/%s", c.httpPort, strings.TrimPrefix(resource, "/"))
    94  }
    95  
    96  // GetNamespace gets namespaces.
    97  func (c *CoordinatorClient) GetNamespace() (admin.NamespaceGetResponse, error) {
    98  	url := c.makeURL("api/v1/services/m3db/namespace")
    99  	logger := c.logger.With(
   100  		zapMethod("getNamespace"), zap.String("url", url))
   101  
   102  	//nolint:noctx
   103  	resp, err := c.client.Get(url)
   104  	if err != nil {
   105  		logger.Error("failed get", zap.Error(err))
   106  		return admin.NamespaceGetResponse{}, err
   107  	}
   108  
   109  	var response admin.NamespaceGetResponse
   110  	if err := toResponse(resp, &response, logger); err != nil {
   111  		return admin.NamespaceGetResponse{}, err
   112  	}
   113  
   114  	return response, nil
   115  }
   116  
   117  // GetPlacement gets placements.
   118  func (c *CoordinatorClient) GetPlacement(opts PlacementRequestOptions) (admin.PlacementGetResponse, error) {
   119  	var handlerurl string
   120  	switch opts.Service {
   121  	case ServiceTypeM3DB:
   122  		handlerurl = placementhandler.M3DBGetURL
   123  	case ServiceTypeM3Aggregator:
   124  		handlerurl = placementhandler.M3AggGetURL
   125  	case ServiceTypeM3Coordinator:
   126  		handlerurl = placementhandler.M3CoordinatorGetURL
   127  	default:
   128  		return admin.PlacementGetResponse{}, errUnknownServiceType
   129  	}
   130  	url := c.makeURL(handlerurl)
   131  	logger := c.logger.With(
   132  		zapMethod("getPlacement"), zap.String("url", url))
   133  
   134  	resp, err := c.makeRequest(logger, url, placementhandler.GetHTTPMethod, nil, placementOptsToMap(opts))
   135  	if err != nil {
   136  		logger.Error("failed get", zap.Error(err))
   137  		return admin.PlacementGetResponse{}, err
   138  	}
   139  
   140  	var response admin.PlacementGetResponse
   141  	if err := toResponse(resp, &response, logger); err != nil {
   142  		return admin.PlacementGetResponse{}, err
   143  	}
   144  
   145  	return response, nil
   146  }
   147  
   148  // InitPlacement initializes placements.
   149  func (c *CoordinatorClient) InitPlacement(
   150  	opts PlacementRequestOptions,
   151  	initRequest admin.PlacementInitRequest,
   152  ) (admin.PlacementGetResponse, error) {
   153  	var handlerurl string
   154  	switch opts.Service {
   155  	case ServiceTypeM3DB:
   156  		handlerurl = placementhandler.M3DBInitURL
   157  	case ServiceTypeM3Aggregator:
   158  		handlerurl = placementhandler.M3AggInitURL
   159  	case ServiceTypeM3Coordinator:
   160  		handlerurl = placementhandler.M3CoordinatorInitURL
   161  	default:
   162  		return admin.PlacementGetResponse{}, errUnknownServiceType
   163  	}
   164  	url := c.makeURL(handlerurl)
   165  	logger := c.logger.With(
   166  		zapMethod("initPlacement"), zap.String("url", url))
   167  
   168  	resp, err := c.makeRequest(logger, url, placementhandler.InitHTTPMethod, &initRequest, placementOptsToMap(opts))
   169  	if err != nil {
   170  		logger.Error("failed init", zap.Error(err))
   171  		return admin.PlacementGetResponse{}, err
   172  	}
   173  
   174  	var response admin.PlacementGetResponse
   175  	if err := toResponse(resp, &response, logger); err != nil {
   176  		return admin.PlacementGetResponse{}, err
   177  	}
   178  
   179  	return response, nil
   180  }
   181  
   182  // DeleteAllPlacements deletes all placements for the specified service.
   183  func (c *CoordinatorClient) DeleteAllPlacements(opts PlacementRequestOptions) error {
   184  	var handlerurl string
   185  	switch opts.Service {
   186  	case ServiceTypeM3DB:
   187  		handlerurl = placementhandler.M3DBDeleteAllURL
   188  	case ServiceTypeM3Aggregator:
   189  		handlerurl = placementhandler.M3AggDeleteAllURL
   190  	case ServiceTypeM3Coordinator:
   191  		handlerurl = placementhandler.M3CoordinatorDeleteAllURL
   192  	default:
   193  		return errUnknownServiceType
   194  	}
   195  	url := c.makeURL(handlerurl)
   196  	logger := c.logger.With(
   197  		zapMethod("deleteAllPlacements"), zap.String("url", url))
   198  
   199  	resp, err := c.makeRequest(
   200  		logger, url, placementhandler.DeleteAllHTTPMethod, nil, placementOptsToMap(opts),
   201  	)
   202  	if err != nil {
   203  		logger.Error("failed to delete all placements", zap.Error(err))
   204  		return err
   205  	}
   206  	defer resp.Body.Close()
   207  
   208  	if resp.StatusCode/100 != 2 {
   209  		logger.Error("status code not 2xx",
   210  			zap.Int("status code", resp.StatusCode),
   211  			zap.String("status", resp.Status))
   212  		return fmt.Errorf("status code %d", resp.StatusCode)
   213  	}
   214  
   215  	logger.Info("placements deleted")
   216  
   217  	return nil
   218  }
   219  
   220  // WaitForNamespace blocks until the given namespace is enabled.
   221  // NB: if the name string is empty, this will instead
   222  // check for a successful response.
   223  func (c *CoordinatorClient) WaitForNamespace(name string) error {
   224  	logger := c.logger.With(zapMethod("waitForNamespace"))
   225  	return c.retryFunc(func() error {
   226  		ns, err := c.GetNamespace()
   227  		if err != nil {
   228  			return err
   229  		}
   230  
   231  		// If no name passed in, instad just check for success.
   232  		if len(name) == 0 {
   233  			return nil
   234  		}
   235  
   236  		nss := ns.GetRegistry().GetNamespaces()
   237  		_, found := nss[name]
   238  		if !found {
   239  			err := fmt.Errorf("no namespace with name %s", name)
   240  			logger.Error("could not get namespace", zap.Error(err))
   241  			return err
   242  		}
   243  
   244  		logger.Info("namespace ready", zap.String("namespace", name))
   245  		return nil
   246  	})
   247  }
   248  
   249  // WaitForInstances blocks until the given instance is available.
   250  func (c *CoordinatorClient) WaitForInstances(
   251  	ids []string,
   252  ) error {
   253  	logger := c.logger.With(zapMethod("waitForPlacement"))
   254  	return c.retryFunc(func() error {
   255  		placement, err := c.GetPlacement(PlacementRequestOptions{Service: ServiceTypeM3DB})
   256  		if err != nil {
   257  			logger.Error("retrying get placement", zap.Error(err))
   258  			return err
   259  		}
   260  
   261  		logger.Info("got placement", zap.Any("placement", placement))
   262  		instances := placement.GetPlacement().GetInstances()
   263  		for _, id := range ids {
   264  			placement, found := instances[id]
   265  			if !found {
   266  				err = fmt.Errorf("no instance with id %s", id)
   267  				logger.Error("could not get instance", zap.Error(err))
   268  				return err
   269  			}
   270  
   271  			if pID := placement.GetId(); pID != id {
   272  				err = fmt.Errorf("id mismatch: instance(%s) != placement(%s)", id, pID)
   273  				logger.Error("could not get instance", zap.Error(err))
   274  				return err
   275  			}
   276  		}
   277  
   278  		logger.Info("instances ready")
   279  		return nil
   280  	})
   281  }
   282  
   283  // WaitForShardsReady waits until all shards gets ready.
   284  func (c *CoordinatorClient) WaitForShardsReady() error {
   285  	logger := c.logger.With(zapMethod("waitForShards"))
   286  	return c.retryFunc(func() error {
   287  		placement, err := c.GetPlacement(PlacementRequestOptions{Service: ServiceTypeM3DB})
   288  		if err != nil {
   289  			logger.Error("retrying get placement", zap.Error(err))
   290  			return err
   291  		}
   292  
   293  		for _, instance := range placement.Placement.Instances {
   294  			for _, shard := range instance.Shards {
   295  				if shard.State == placementpb.ShardState_INITIALIZING {
   296  					err = fmt.Errorf("at least shard %d of dbnode %s still initializing", shard.Id, instance.Id)
   297  					logger.Error("shards still are initializing", zap.Error(err))
   298  					return err
   299  				}
   300  			}
   301  		}
   302  		return nil
   303  	})
   304  }
   305  
   306  // WaitForClusterReady waits until the cluster is ready to receive reads and writes.
   307  func (c *CoordinatorClient) WaitForClusterReady() error {
   308  	var (
   309  		url    = c.makeURL("ready")
   310  		logger = c.logger.With(zapMethod("waitForClusterReady"), zap.String("url", url))
   311  	)
   312  	return c.retryFunc(func() error {
   313  		req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
   314  		if err != nil {
   315  			logger.Error("failed to create request", zap.Error(err))
   316  			return err
   317  		}
   318  
   319  		resp, err := c.client.Do(req)
   320  		if err != nil {
   321  			logger.Error("failed checking cluster readiness", zap.Error(err))
   322  			return err
   323  		}
   324  		defer resp.Body.Close()
   325  
   326  		if resp.StatusCode != 200 {
   327  			err = errors.New("non-200 status code received")
   328  
   329  			body, rerr := ioutil.ReadAll(resp.Body)
   330  			if rerr != nil {
   331  				logger.Warn("failed parse response body", zap.Error(rerr))
   332  				body = []byte("")
   333  			}
   334  
   335  			logger.Error("failed to check cluster readiness", zap.Error(err),
   336  				zap.String("responseBody", string(body)),
   337  			)
   338  			return err
   339  		}
   340  
   341  		logger.Info("cluster ready to receive reads and writes")
   342  
   343  		return nil
   344  	})
   345  }
   346  
   347  // CreateDatabase creates a database.
   348  func (c *CoordinatorClient) CreateDatabase(
   349  	addRequest admin.DatabaseCreateRequest,
   350  ) (admin.DatabaseCreateResponse, error) {
   351  	url := c.makeURL("api/v1/database/create")
   352  	logger := c.logger.With(
   353  		zapMethod("createDatabase"), zap.String("url", url),
   354  		zap.String("request", addRequest.String()))
   355  
   356  	resp, err := c.makeRequest(logger, url, http.MethodPost, &addRequest, nil)
   357  	if err != nil {
   358  		logger.Error("failed post", zap.Error(err))
   359  		return admin.DatabaseCreateResponse{}, err
   360  	}
   361  
   362  	var response admin.DatabaseCreateResponse
   363  	if err := toResponse(resp, &response, logger); err != nil {
   364  		logger.Error("failed response", zap.Error(err))
   365  		return admin.DatabaseCreateResponse{}, err
   366  	}
   367  
   368  	if err = c.setNamespaceReady(addRequest.NamespaceName); err != nil {
   369  		logger.Error("failed to set namespace to ready state",
   370  			zap.Error(err),
   371  			zap.String("namespace", addRequest.NamespaceName),
   372  		)
   373  		return response, err
   374  	}
   375  
   376  	logger.Info("created database")
   377  	return response, nil
   378  }
   379  
   380  // AddNamespace adds a namespace.
   381  func (c *CoordinatorClient) AddNamespace(
   382  	addRequest admin.NamespaceAddRequest,
   383  ) (admin.NamespaceGetResponse, error) {
   384  	url := c.makeURL("api/v1/services/m3db/namespace")
   385  	logger := c.logger.With(
   386  		zapMethod("addNamespace"), zap.String("url", url),
   387  		zap.String("request", addRequest.String()))
   388  
   389  	resp, err := c.makeRequest(logger, url, http.MethodPost, &addRequest, nil)
   390  	if err != nil {
   391  		logger.Error("failed post", zap.Error(err))
   392  		return admin.NamespaceGetResponse{}, err
   393  	}
   394  
   395  	var response admin.NamespaceGetResponse
   396  	if err := toResponse(resp, &response, logger); err != nil {
   397  		return admin.NamespaceGetResponse{}, err
   398  	}
   399  
   400  	if err = c.setNamespaceReady(addRequest.Name); err != nil {
   401  		logger.Error("failed to set namespace to ready state", zap.Error(err), zap.String("namespace", addRequest.Name))
   402  		return response, err
   403  	}
   404  
   405  	return response, nil
   406  }
   407  
   408  // UpdateNamespace updates the namespace.
   409  func (c *CoordinatorClient) UpdateNamespace(
   410  	req admin.NamespaceUpdateRequest,
   411  ) (admin.NamespaceGetResponse, error) {
   412  	url := c.makeURL("api/v1/services/m3db/namespace")
   413  	logger := c.logger.With(
   414  		zapMethod("updateNamespace"), zap.String("url", url),
   415  		zap.String("request", req.String()))
   416  
   417  	resp, err := c.makeRequest(logger, url, http.MethodPut, &req, nil)
   418  	if err != nil {
   419  		logger.Error("failed to update namespace", zap.Error(err))
   420  		return admin.NamespaceGetResponse{}, err
   421  	}
   422  
   423  	var response admin.NamespaceGetResponse
   424  	if err := toResponse(resp, &response, logger); err != nil {
   425  		return admin.NamespaceGetResponse{}, err
   426  	}
   427  
   428  	return response, nil
   429  }
   430  
   431  func (c *CoordinatorClient) setNamespaceReady(name string) error {
   432  	url := c.makeURL("api/v1/services/m3db/namespace/ready")
   433  	logger := c.logger.With(
   434  		zapMethod("setNamespaceReady"), zap.String("url", url),
   435  		zap.String("namespace", name))
   436  
   437  	_, err := c.makeRequest(logger, url, http.MethodPost, // nolint: bodyclose
   438  		&admin.NamespaceReadyRequest{
   439  			Name:  name,
   440  			Force: true,
   441  		}, nil)
   442  	return err
   443  }
   444  
   445  // DeleteNamespace removes the namespace.
   446  func (c *CoordinatorClient) DeleteNamespace(namespaceID string) error {
   447  	url := c.makeURL("api/v1/services/m3db/namespace/" + namespaceID)
   448  	logger := c.logger.With(zapMethod("deleteNamespace"), zap.String("url", url))
   449  
   450  	if _, err := c.makeRequest(logger, url, http.MethodDelete, nil, nil); err != nil { // nolint: bodyclose
   451  		logger.Error("failed to delete namespace", zap.Error(err))
   452  		return err
   453  	}
   454  	return nil
   455  }
   456  
   457  //nolint:dupl
   458  // InitM3msgTopic initializes an m3msg topic
   459  func (c *CoordinatorClient) InitM3msgTopic(
   460  	topicOpts M3msgTopicOptions,
   461  	initRequest admin.TopicInitRequest,
   462  ) (admin.TopicGetResponse, error) {
   463  	url := c.makeURL(topic.InitURL)
   464  	logger := c.logger.With(
   465  		zapMethod("initM3msgTopic"),
   466  		zap.String("url", url),
   467  		zap.String("request", initRequest.String()),
   468  		zap.String("topic", fmt.Sprintf("%v", topicOpts)))
   469  
   470  	resp, err := c.makeRequest(logger, url, topic.InitHTTPMethod, &initRequest, m3msgTopicOptionsToMap(topicOpts))
   471  	if err != nil {
   472  		logger.Error("failed post", zap.Error(err))
   473  		return admin.TopicGetResponse{}, err
   474  	}
   475  
   476  	var response admin.TopicGetResponse
   477  	if err := toResponse(resp, &response, logger); err != nil {
   478  		logger.Error("failed response", zap.Error(err))
   479  		return admin.TopicGetResponse{}, err
   480  	}
   481  
   482  	logger.Info("topic initialized")
   483  	return response, nil
   484  }
   485  
   486  // GetM3msgTopic fetches an m3msg topic
   487  func (c *CoordinatorClient) GetM3msgTopic(
   488  	topicOpts M3msgTopicOptions,
   489  ) (admin.TopicGetResponse, error) {
   490  	url := c.makeURL(topic.GetURL)
   491  	logger := c.logger.With(
   492  		zapMethod("getM3msgTopic"), zap.String("url", url),
   493  		zap.String("topic", fmt.Sprintf("%v", topicOpts)))
   494  
   495  	resp, err := c.makeRequest(logger, url, topic.GetHTTPMethod, nil, m3msgTopicOptionsToMap(topicOpts))
   496  	if err != nil {
   497  		logger.Error("failed get", zap.Error(err))
   498  		return admin.TopicGetResponse{}, err
   499  	}
   500  
   501  	var response admin.TopicGetResponse
   502  	if err := toResponse(resp, &response, logger); err != nil {
   503  		logger.Error("failed response", zap.Error(err))
   504  		return admin.TopicGetResponse{}, err
   505  	}
   506  
   507  	logger.Info("topic get")
   508  	return response, nil
   509  }
   510  
   511  //nolint:dupl
   512  // AddM3msgTopicConsumer adds a consumer service to an m3msg topic
   513  func (c *CoordinatorClient) AddM3msgTopicConsumer(
   514  	topicOpts M3msgTopicOptions,
   515  	addRequest admin.TopicAddRequest,
   516  ) (admin.TopicGetResponse, error) {
   517  	url := c.makeURL(topic.AddURL)
   518  	logger := c.logger.With(
   519  		zapMethod("addM3msgTopicConsumer"),
   520  		zap.String("url", url),
   521  		zap.String("request", addRequest.String()),
   522  		zap.String("topic", fmt.Sprintf("%v", topicOpts)))
   523  
   524  	resp, err := c.makeRequest(logger, url, topic.AddHTTPMethod, &addRequest, m3msgTopicOptionsToMap(topicOpts))
   525  	if err != nil {
   526  		logger.Error("failed post", zap.Error(err))
   527  		return admin.TopicGetResponse{}, err
   528  	}
   529  
   530  	var response admin.TopicGetResponse
   531  	if err := toResponse(resp, &response, logger); err != nil {
   532  		logger.Error("failed response", zap.Error(err))
   533  		return admin.TopicGetResponse{}, err
   534  	}
   535  
   536  	logger.Info("topic consumer added")
   537  	return response, nil
   538  }
   539  
   540  func placementOptsToMap(opts PlacementRequestOptions) map[string]string {
   541  	return map[string]string{
   542  		headers.HeaderClusterEnvironmentName: opts.Env,
   543  		headers.HeaderClusterZoneName:        opts.Zone,
   544  	}
   545  }
   546  
   547  func m3msgTopicOptionsToMap(opts M3msgTopicOptions) map[string]string {
   548  	return map[string]string{
   549  		headers.HeaderClusterEnvironmentName: opts.Env,
   550  		headers.HeaderClusterZoneName:        opts.Zone,
   551  		topic.HeaderTopicName:                opts.TopicName,
   552  	}
   553  }
   554  
   555  // WriteCarbon writes a carbon metric datapoint at a given time.
   556  func (c *CoordinatorClient) WriteCarbon(
   557  	url string, metric string, v float64, t time.Time,
   558  ) error {
   559  	logger := c.logger.With(
   560  		zapMethod("writeCarbon"), zap.String("url", url),
   561  		zap.String("at time", time.Now().String()),
   562  		zap.String("at ts", t.String()))
   563  
   564  	con, err := net.Dial("tcp", url)
   565  	if err != nil {
   566  		logger.Error("could not dial", zap.Error(err))
   567  		return err
   568  	}
   569  
   570  	write := fmt.Sprintf("%s %f %d", metric, v, t.Unix())
   571  	logger.Info("writing", zap.String("metric", write))
   572  	n, err := con.Write([]byte(write))
   573  	if err != nil {
   574  		logger.Error("could not write", zap.Error(err))
   575  	}
   576  
   577  	if n != len(write) {
   578  		err := fmt.Errorf("wrote %d, wanted %d", n, len(write))
   579  		logger.Error("write failure", zap.Error(err))
   580  		return err
   581  	}
   582  
   583  	logger.Info("write success", zap.Int("bytes written", n))
   584  	return con.Close()
   585  }
   586  
   587  // WriteProm writes a prometheus metric. Takes tags/labels as a map for convenience.
   588  func (c *CoordinatorClient) WriteProm(
   589  	name string,
   590  	tags map[string]string,
   591  	samples []prompb.Sample,
   592  	headers Headers,
   593  ) error {
   594  	labels := make([]prompb.Label, 0, len(tags)+1)
   595  
   596  	labels = append(labels, prompb.Label{
   597  		Name:  []byte(model.MetricNameLabel),
   598  		Value: []byte(name),
   599  	})
   600  
   601  	for tag, value := range tags {
   602  		labels = append(labels, prompb.Label{
   603  			Name:  []byte(tag),
   604  			Value: []byte(value),
   605  		})
   606  	}
   607  
   608  	writeRequest := prompb.WriteRequest{
   609  		Timeseries: []prompb.TimeSeries{
   610  			{
   611  				Labels:  labels,
   612  				Samples: samples,
   613  			},
   614  		},
   615  	}
   616  
   617  	return c.WritePromWithRequest(writeRequest, headers)
   618  }
   619  
   620  // WritePromWithRequest executes a prometheus write request. Allows you to
   621  // provide the request directly which is useful for batch metric requests.
   622  func (c *CoordinatorClient) WritePromWithRequest(writeRequest prompb.WriteRequest, headers Headers) error {
   623  	url := c.makeURL("api/v1/prom/remote/write")
   624  
   625  	logger := c.logger.With(
   626  		zapMethod("writeProm"), zap.String("url", url),
   627  		zap.String("request", writeRequest.String()))
   628  
   629  	body, err := proto.Marshal(&writeRequest)
   630  	if err != nil {
   631  		logger.Error("failed marshaling request message", zap.Error(err))
   632  		return err
   633  	}
   634  	data := bytes.NewBuffer(snappy.Encode(nil, body))
   635  
   636  	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, data)
   637  	if err != nil {
   638  		logger.Error("failed constructing request", zap.Error(err))
   639  		return err
   640  	}
   641  	for key, vals := range headers {
   642  		for _, val := range vals {
   643  			req.Header.Add(key, val)
   644  		}
   645  	}
   646  	req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeProtobuf)
   647  
   648  	resp, err := c.client.Do(req)
   649  	if err != nil {
   650  		logger.Error("failed making a request", zap.Error(err))
   651  		return err
   652  	}
   653  	defer resp.Body.Close()
   654  	if resp.StatusCode < 200 || resp.StatusCode > 299 {
   655  		logger.Error("status code not 2xx",
   656  			zap.Int("status code", resp.StatusCode),
   657  			zap.String("status", resp.Status))
   658  		return fmt.Errorf("status code %d", resp.StatusCode)
   659  	}
   660  
   661  	return nil
   662  }
   663  
   664  func (c *CoordinatorClient) makeRequest(
   665  	logger *zap.Logger,
   666  	url string,
   667  	method string,
   668  	body proto.Message,
   669  	header map[string]string,
   670  ) (*http.Response, error) {
   671  	data := bytes.NewBuffer(nil)
   672  	if body != nil {
   673  		if err := (&jsonpb.Marshaler{}).Marshal(data, body); err != nil {
   674  			logger.Error("failed to marshal", zap.Error(err))
   675  
   676  			return nil, fmt.Errorf("failed to marshal: %w", err)
   677  		}
   678  	}
   679  
   680  	req, err := http.NewRequestWithContext(context.Background(), method, url, data)
   681  	if err != nil {
   682  		logger.Error("failed to construct request", zap.Error(err))
   683  
   684  		return nil, fmt.Errorf("failed to construct request: %w", err)
   685  	}
   686  
   687  	req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeJSON)
   688  	for k, v := range header {
   689  		req.Header.Add(k, v)
   690  	}
   691  
   692  	return c.client.Do(req)
   693  }
   694  
   695  // ApplyKVUpdate applies a KV update.
   696  func (c *CoordinatorClient) ApplyKVUpdate(update string) error {
   697  	url := c.makeURL("api/v1/kvstore")
   698  
   699  	logger := c.logger.With(
   700  		zapMethod("ApplyKVUpdate"), zap.String("url", url),
   701  		zap.String("update", update))
   702  
   703  	data := bytes.NewBuffer([]byte(update))
   704  	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, data)
   705  	if err != nil {
   706  		logger.Error("failed to construct request", zap.Error(err))
   707  		return fmt.Errorf("failed to construct request: %w", err)
   708  	}
   709  
   710  	req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeJSON)
   711  
   712  	resp, err := c.client.Do(req)
   713  	if err != nil {
   714  		logger.Error("failed to apply request", zap.Error(err))
   715  		return fmt.Errorf("failed to apply request: %w", err)
   716  	}
   717  
   718  	bs, err := ioutil.ReadAll(resp.Body)
   719  	if err != nil {
   720  		logger.Error("failed to read body", zap.Error(err))
   721  		return fmt.Errorf("failed to read body: %w", err)
   722  	}
   723  
   724  	logger.Info("applied KV update", zap.ByteString("response", bs))
   725  	_ = resp.Body.Close()
   726  	return nil
   727  }
   728  
   729  func (c *CoordinatorClient) query(
   730  	verifier ResponseVerifier, query string, headers map[string][]string,
   731  ) error {
   732  	url := c.makeURL(query)
   733  	logger := c.logger.With(
   734  		zapMethod("query"), zap.String("url", url), zap.Any("headers", headers))
   735  	logger.Info("running")
   736  	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
   737  	if err != nil {
   738  		return err
   739  	}
   740  
   741  	if headers != nil {
   742  		req.Header = headers
   743  	}
   744  
   745  	resp, err := c.client.Do(req)
   746  	if err != nil {
   747  		logger.Error("failed get", zap.Error(err))
   748  		return err
   749  	}
   750  
   751  	defer resp.Body.Close()
   752  	b, err := ioutil.ReadAll(resp.Body)
   753  
   754  	return verifier(resp.StatusCode, resp.Header, string(b), err)
   755  }
   756  
   757  // InstantQuery runs an instant query with provided headers
   758  func (c *CoordinatorClient) InstantQuery(req QueryRequest, headers Headers) (model.Vector, error) {
   759  	return c.instantQuery(req, route.QueryURL, headers)
   760  }
   761  
   762  // InstantQueryWithEngine runs an instant query with provided headers and the specified
   763  // query engine.
   764  func (c *CoordinatorClient) InstantQueryWithEngine(
   765  	req QueryRequest,
   766  	engine options.QueryEngine,
   767  	headers Headers,
   768  ) (model.Vector, error) {
   769  	if engine == options.M3QueryEngine {
   770  		return c.instantQuery(req, native.M3QueryReadInstantURL, headers)
   771  	} else if engine == options.PrometheusEngine {
   772  		return c.instantQuery(req, native.PrometheusReadInstantURL, headers)
   773  	}
   774  	return nil, fmt.Errorf("unknown query engine: %s", engine)
   775  }
   776  
   777  func (c *CoordinatorClient) instantQuery(
   778  	req QueryRequest,
   779  	queryRoute string,
   780  	headers Headers,
   781  ) (model.Vector, error) {
   782  	queryStr := fmt.Sprintf("%s?query=%s", queryRoute, req.Query)
   783  	if req.Time != nil {
   784  		queryStr = fmt.Sprintf("%s&time=%d", queryStr, req.Time.Unix())
   785  	}
   786  
   787  	resp, err := c.runQuery(queryStr, headers)
   788  	if err != nil {
   789  		return nil, err
   790  	}
   791  
   792  	var parsedResp jsonInstantQueryResponse
   793  	if err := json.Unmarshal([]byte(resp), &parsedResp); err != nil {
   794  		return nil, err
   795  	}
   796  
   797  	return parsedResp.Data.Result, nil
   798  }
   799  
   800  type jsonInstantQueryResponse struct {
   801  	Status string
   802  	Data   vectorResult
   803  }
   804  
   805  type vectorResult struct {
   806  	ResultType model.ValueType
   807  	Result     model.Vector
   808  }
   809  
   810  // RangeQuery runs a range query with provided headers
   811  func (c *CoordinatorClient) RangeQuery(
   812  	req RangeQueryRequest,
   813  	headers Headers,
   814  ) (model.Matrix, error) {
   815  	return c.rangeQuery(req, route.QueryRangeURL, headers)
   816  }
   817  
   818  // RangeQueryWithEngine runs a range query with provided headers and the specified
   819  // query engine.
   820  func (c *CoordinatorClient) RangeQueryWithEngine(
   821  	req RangeQueryRequest,
   822  	engine options.QueryEngine,
   823  	headers Headers,
   824  ) (model.Matrix, error) {
   825  	if engine == options.M3QueryEngine {
   826  		return c.rangeQuery(req, native.M3QueryReadURL, headers)
   827  	} else if engine == options.PrometheusEngine {
   828  		return c.rangeQuery(req, native.PrometheusReadURL, headers)
   829  	}
   830  	return nil, fmt.Errorf("unknown query engine: %s", engine)
   831  }
   832  
   833  func (c *CoordinatorClient) rangeQuery(
   834  	req RangeQueryRequest,
   835  	queryRoute string,
   836  	headers Headers,
   837  ) (model.Matrix, error) {
   838  	if req.Step == 0 {
   839  		req.Step = 15 * time.Second // default step is 15 seconds.
   840  	}
   841  	queryStr := fmt.Sprintf(
   842  		"%s?query=%s&start=%d&end=%d&step=%f",
   843  		queryRoute, req.Query,
   844  		req.Start.Unix(),
   845  		req.End.Unix(),
   846  		req.Step.Seconds(),
   847  	)
   848  
   849  	resp, err := c.runQuery(queryStr, headers)
   850  	if err != nil {
   851  		return nil, err
   852  	}
   853  
   854  	var parsedResp jsonRangeQueryResponse
   855  	if err := json.Unmarshal([]byte(resp), &parsedResp); err != nil {
   856  		return nil, err
   857  	}
   858  
   859  	return parsedResp.Data.Result, nil
   860  }
   861  
   862  // LabelNames return matching label names based on the request.
   863  func (c *CoordinatorClient) LabelNames(
   864  	req LabelNamesRequest,
   865  	headers Headers,
   866  ) (model.LabelNames, error) {
   867  	urlPathAndQuery := fmt.Sprintf("%s?%s", route.LabelNamesURL, req.String())
   868  	resp, err := c.runQuery(urlPathAndQuery, headers)
   869  	if err != nil {
   870  		return nil, err
   871  	}
   872  
   873  	var parsedResp labelResponse
   874  	if err := json.Unmarshal([]byte(resp), &parsedResp); err != nil {
   875  		return nil, err
   876  	}
   877  
   878  	labelNames := make(model.LabelNames, 0, len(parsedResp.Data))
   879  	for _, label := range parsedResp.Data {
   880  		labelNames = append(labelNames, model.LabelName(label))
   881  	}
   882  
   883  	return labelNames, nil
   884  }
   885  
   886  // LabelValues return matching label values based on the request.
   887  func (c *CoordinatorClient) LabelValues(
   888  	req LabelValuesRequest,
   889  	headers Headers,
   890  ) (model.LabelValues, error) {
   891  	urlPathAndQuery := fmt.Sprintf("%s?%s",
   892  		path.Join(route.Prefix, "label", req.LabelName, "values"),
   893  		req.String())
   894  	resp, err := c.runQuery(urlPathAndQuery, headers)
   895  	if err != nil {
   896  		return nil, err
   897  	}
   898  
   899  	var parsedResp labelResponse
   900  	if err := json.Unmarshal([]byte(resp), &parsedResp); err != nil {
   901  		return nil, err
   902  	}
   903  
   904  	labelValues := make(model.LabelValues, 0, len(parsedResp.Data))
   905  	for _, label := range parsedResp.Data {
   906  		labelValues = append(labelValues, model.LabelValue(label))
   907  	}
   908  
   909  	return labelValues, nil
   910  }
   911  
   912  // Series returns matching series based on the request.
   913  func (c *CoordinatorClient) Series(
   914  	req SeriesRequest,
   915  	headers Headers,
   916  ) ([]model.Metric, error) {
   917  	urlPathAndQuery := fmt.Sprintf("%s?%s", route.SeriesMatchURL, req.String())
   918  	resp, err := c.runQuery(urlPathAndQuery, headers)
   919  	if err != nil {
   920  		return nil, err
   921  	}
   922  
   923  	var parsedResp seriesResponse
   924  	if err := json.Unmarshal([]byte(resp), &parsedResp); err != nil {
   925  		return nil, err
   926  	}
   927  
   928  	series := make([]model.Metric, 0, len(parsedResp.Data))
   929  	for _, labels := range parsedResp.Data {
   930  		labelSet := make(model.LabelSet)
   931  		for name, val := range labels {
   932  			labelSet[model.LabelName(name)] = model.LabelValue(val)
   933  		}
   934  		series = append(series, model.Metric(labelSet))
   935  	}
   936  
   937  	return series, nil
   938  }
   939  
   940  type jsonRangeQueryResponse struct {
   941  	Status string
   942  	Data   matrixResult
   943  }
   944  
   945  type matrixResult struct {
   946  	ResultType model.ValueType
   947  	Result     model.Matrix
   948  }
   949  
   950  type labelResponse struct {
   951  	Status string
   952  	Data   []string
   953  }
   954  
   955  type seriesResponse struct {
   956  	Status string
   957  	Data   []map[string]string
   958  }
   959  
   960  func (c *CoordinatorClient) runQuery(
   961  	query string, headers map[string][]string,
   962  ) (string, error) {
   963  	url := c.makeURL(query)
   964  	logger := c.logger.With(
   965  		zapMethod("query"), zap.String("url", url), zap.Any("headers", headers))
   966  	logger.Info("running")
   967  	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
   968  	if err != nil {
   969  		return "", err
   970  	}
   971  
   972  	if headers != nil {
   973  		req.Header = headers
   974  	}
   975  
   976  	resp, err := c.client.Do(req)
   977  	if err != nil {
   978  		logger.Error("failed get", zap.Error(err))
   979  		return "", err
   980  	}
   981  
   982  	defer resp.Body.Close()
   983  	b, err := ioutil.ReadAll(resp.Body)
   984  
   985  	if status := resp.StatusCode; status != http.StatusOK {
   986  		return "", fmt.Errorf("query response status not OK, received %v. error=%v",
   987  			status, string(b))
   988  	}
   989  
   990  	if contentType, ok := resp.Header["Content-Type"]; !ok {
   991  		return "", fmt.Errorf("missing Content-Type header")
   992  	} else if len(contentType) != 1 || contentType[0] != "application/json" { //nolint:goconst
   993  		return "", fmt.Errorf("expected json content type, got %v", contentType)
   994  	}
   995  
   996  	return string(b), err
   997  }
   998  
   999  // RunQuery runs the given query with a given verification function.
  1000  func (c *CoordinatorClient) RunQuery(
  1001  	verifier ResponseVerifier, query string, headers map[string][]string,
  1002  ) error {
  1003  	logger := c.logger.With(zapMethod("runQuery"),
  1004  		zap.String("query", query))
  1005  	err := c.retryFunc(func() error {
  1006  		err := c.query(verifier, query, headers)
  1007  		if err != nil {
  1008  			logger.Info("retrying", zap.Error(err))
  1009  		}
  1010  
  1011  		return err
  1012  	})
  1013  	if err != nil {
  1014  		logger.Error("failed run", zap.Error(err))
  1015  	}
  1016  
  1017  	return err
  1018  }
  1019  
  1020  func toResponse(
  1021  	resp *http.Response,
  1022  	response proto.Message,
  1023  	logger *zap.Logger,
  1024  ) error {
  1025  	b, err := ioutil.ReadAll(resp.Body)
  1026  	if err != nil {
  1027  		logger.Error("could not read body", zap.Error(err))
  1028  		return err
  1029  	}
  1030  
  1031  	defer resp.Body.Close()
  1032  	if resp.StatusCode/100 != 2 {
  1033  		logger.Error("status code not 2xx",
  1034  			zap.Int("status code", resp.StatusCode),
  1035  			zap.String("status", resp.Status))
  1036  		return fmt.Errorf("status code %d", resp.StatusCode)
  1037  	}
  1038  
  1039  	err = jsonpb.Unmarshal(bytes.NewReader(b), response)
  1040  	if err != nil {
  1041  		logger.Error("unable to unmarshal response",
  1042  			zap.Error(err),
  1043  			zap.Any("response", response))
  1044  		return err
  1045  	}
  1046  
  1047  	return nil
  1048  }
  1049  
  1050  // GraphiteQuery retrieves graphite raw data.
  1051  func (c *CoordinatorClient) GraphiteQuery(
  1052  	graphiteReq GraphiteQueryRequest,
  1053  ) ([]Datapoint, error) {
  1054  	if graphiteReq.From.IsZero() {
  1055  		graphiteReq.From = time.Now().Add(-24 * time.Hour)
  1056  	}
  1057  	if graphiteReq.Until.IsZero() {
  1058  		graphiteReq.Until = time.Now()
  1059  	}
  1060  
  1061  	queryStr := fmt.Sprintf(
  1062  		"%s?target=%s&from=%d&until=%d",
  1063  		graphite.ReadURL, graphiteReq.Target,
  1064  		graphiteReq.From.Unix(),
  1065  		graphiteReq.Until.Unix(),
  1066  	)
  1067  
  1068  	url := c.makeURL(queryStr)
  1069  	logger := c.logger.With(
  1070  		zapMethod("graphiteQuery"), zap.String("url", url))
  1071  	logger.Info("running")
  1072  	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
  1073  	if err != nil {
  1074  		return nil, err
  1075  	}
  1076  
  1077  	resp, err := c.client.Do(req)
  1078  	if err != nil {
  1079  		logger.Error("failed get", zap.Error(err))
  1080  		return nil, err
  1081  	}
  1082  
  1083  	defer resp.Body.Close()
  1084  	b, err := ioutil.ReadAll(resp.Body)
  1085  	if err != nil {
  1086  		return nil, err
  1087  	}
  1088  
  1089  	if status := resp.StatusCode; status != http.StatusOK {
  1090  		return nil, fmt.Errorf("query response status not OK, received %v %s", status, resp.Status)
  1091  	}
  1092  
  1093  	var parsedResp jsonGraphiteQueryResponse
  1094  	if err := json.Unmarshal(b, &parsedResp); err != nil {
  1095  		return nil, err
  1096  	}
  1097  
  1098  	if len(parsedResp) == 0 {
  1099  		return nil, nil
  1100  	}
  1101  
  1102  	results := make([]Datapoint, 0, len(parsedResp[0].Datapoints))
  1103  	for _, dp := range parsedResp[0].Datapoints {
  1104  		if len(dp) != 2 {
  1105  			return nil, fmt.Errorf("failed to parse response: %s", string(b))
  1106  		}
  1107  
  1108  		results = append(results, Datapoint{
  1109  			Value:     dp[0],
  1110  			Timestamp: int64(*dp[1]),
  1111  		})
  1112  	}
  1113  
  1114  	return results, nil
  1115  }
  1116  
  1117  type jsonGraphiteQueryResponse []series
  1118  
  1119  type series struct {
  1120  	Target     string
  1121  	Datapoints []tuple
  1122  	StepSizeMs int
  1123  }
  1124  
  1125  type tuple []*float64