| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | // Copyright 2016 The Prometheus Authors
 | 
					
						
							|  |  |  | // Licensed under the Apache License, Version 2.0 (the "License");
 | 
					
						
							|  |  |  | // you may not use this file except in compliance with the License.
 | 
					
						
							|  |  |  | // You may obtain a copy of the License at
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Unless required by applicable law or agreed to in writing, software
 | 
					
						
							|  |  |  | // distributed under the License is distributed on an "AS IS" BASIS,
 | 
					
						
							|  |  |  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
					
						
							|  |  |  | // See the License for the specific language governing permissions and
 | 
					
						
							|  |  |  | // limitations under the License.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package remote | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bufio" | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2017-10-25 12:21:42 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"io/ioutil" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2019-08-28 21:25:28 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-02 06:03:46 +08:00
										 |  |  | 	"github.com/gogo/protobuf/proto" | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	"github.com/golang/snappy" | 
					
						
							| 
									
										
										
										
											2019-03-26 07:01:12 +08:00
										 |  |  | 	"github.com/pkg/errors" | 
					
						
							|  |  |  | 	config_util "github.com/prometheus/common/config" | 
					
						
							| 
									
										
										
										
											2017-10-25 12:21:42 +08:00
										 |  |  | 	"github.com/prometheus/common/model" | 
					
						
							| 
									
										
										
										
											2018-11-23 22:49:49 +08:00
										 |  |  | 	"github.com/prometheus/common/version" | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 05:06:35 +08:00
										 |  |  | 	"github.com/prometheus/prometheus/prompb" | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const maxErrMsgLen = 256 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-23 22:49:49 +08:00
										 |  |  | var userAgent = fmt.Sprintf("Prometheus/%s", version.Version) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | // Client allows reading and writing from/to a remote HTTP endpoint.
 | 
					
						
							|  |  |  | type Client struct { | 
					
						
							| 
									
										
										
										
											2017-11-12 09:15:27 +08:00
										 |  |  | 	index   int // Used to differentiate clients in metrics.
 | 
					
						
							| 
									
										
											  
											
												Refactor SD configuration to remove `config` dependency (#3629)
* refactor: move targetGroup struct and CheckOverflow() to their own package
* refactor: move auth and security related structs to a utility package, fix import error in utility package
* refactor: Azure SD, remove SD struct from config
* refactor: DNS SD, remove SD struct from config into dns package
* refactor: ec2 SD, move SD struct from config into the ec2 package
* refactor: file SD, move SD struct from config to file discovery package
* refactor: gce, move SD struct from config to gce discovery package
* refactor: move HTTPClientConfig and URL into util/config, fix import error in httputil
* refactor: consul, move SD struct from config into consul discovery package
* refactor: marathon, move SD struct from config into marathon discovery package
* refactor: triton, move SD struct from config to triton discovery package, fix test
* refactor: zookeeper, move SD structs from config to zookeeper discovery package
* refactor: openstack, remove SD struct from config, move into openstack discovery package
* refactor: kubernetes, move SD struct from config into kubernetes discovery package
* refactor: notifier, use targetgroup package instead of config
* refactor: tests for file, marathon, triton SD - use targetgroup package instead of config.TargetGroup
* refactor: retrieval, use targetgroup package instead of config.TargetGroup
* refactor: storage, use config util package
* refactor: discovery manager, use targetgroup package instead of config.TargetGroup
* refactor: use HTTPClient and TLS config from configUtil instead of config
* refactor: tests, use targetgroup package instead of config.TargetGroup
* refactor: fix tagetgroup.Group pointers that were removed by mistake
* refactor: openstack, kubernetes: drop prefixes
* refactor: remove import aliases forced due to vscode bug
* refactor: move main SD struct out of config into discovery/config
* refactor: rename configUtil to config_util
* refactor: rename yamlUtil to yaml_config
* refactor: kubernetes, remove prefixes
* refactor: move the TargetGroup package to discovery/
* refactor: fix order of imports
											
										 
											2017-12-30 04:01:34 +08:00
										 |  |  | 	url     *config_util.URL | 
					
						
							| 
									
										
										
										
											2017-11-12 09:15:27 +08:00
										 |  |  | 	client  *http.Client | 
					
						
							|  |  |  | 	timeout time.Duration | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-23 21:53:43 +08:00
										 |  |  | // ClientConfig configures a Client.
 | 
					
						
							|  |  |  | type ClientConfig struct { | 
					
						
							| 
									
										
											  
											
												Refactor SD configuration to remove `config` dependency (#3629)
* refactor: move targetGroup struct and CheckOverflow() to their own package
* refactor: move auth and security related structs to a utility package, fix import error in utility package
* refactor: Azure SD, remove SD struct from config
* refactor: DNS SD, remove SD struct from config into dns package
* refactor: ec2 SD, move SD struct from config into the ec2 package
* refactor: file SD, move SD struct from config to file discovery package
* refactor: gce, move SD struct from config to gce discovery package
* refactor: move HTTPClientConfig and URL into util/config, fix import error in httputil
* refactor: consul, move SD struct from config into consul discovery package
* refactor: marathon, move SD struct from config into marathon discovery package
* refactor: triton, move SD struct from config to triton discovery package, fix test
* refactor: zookeeper, move SD structs from config to zookeeper discovery package
* refactor: openstack, remove SD struct from config, move into openstack discovery package
* refactor: kubernetes, move SD struct from config into kubernetes discovery package
* refactor: notifier, use targetgroup package instead of config
* refactor: tests for file, marathon, triton SD - use targetgroup package instead of config.TargetGroup
* refactor: retrieval, use targetgroup package instead of config.TargetGroup
* refactor: storage, use config util package
* refactor: discovery manager, use targetgroup package instead of config.TargetGroup
* refactor: use HTTPClient and TLS config from configUtil instead of config
* refactor: tests, use targetgroup package instead of config.TargetGroup
* refactor: fix tagetgroup.Group pointers that were removed by mistake
* refactor: openstack, kubernetes: drop prefixes
* refactor: remove import aliases forced due to vscode bug
* refactor: move main SD struct out of config into discovery/config
* refactor: rename configUtil to config_util
* refactor: rename yamlUtil to yaml_config
* refactor: kubernetes, remove prefixes
* refactor: move the TargetGroup package to discovery/
* refactor: fix order of imports
											
										 
											2017-12-30 04:01:34 +08:00
										 |  |  | 	URL              *config_util.URL | 
					
						
							| 
									
										
										
										
											2017-10-23 21:53:43 +08:00
										 |  |  | 	Timeout          model.Duration | 
					
						
							| 
									
										
											  
											
												Refactor SD configuration to remove `config` dependency (#3629)
* refactor: move targetGroup struct and CheckOverflow() to their own package
* refactor: move auth and security related structs to a utility package, fix import error in utility package
* refactor: Azure SD, remove SD struct from config
* refactor: DNS SD, remove SD struct from config into dns package
* refactor: ec2 SD, move SD struct from config into the ec2 package
* refactor: file SD, move SD struct from config to file discovery package
* refactor: gce, move SD struct from config to gce discovery package
* refactor: move HTTPClientConfig and URL into util/config, fix import error in httputil
* refactor: consul, move SD struct from config into consul discovery package
* refactor: marathon, move SD struct from config into marathon discovery package
* refactor: triton, move SD struct from config to triton discovery package, fix test
* refactor: zookeeper, move SD structs from config to zookeeper discovery package
* refactor: openstack, remove SD struct from config, move into openstack discovery package
* refactor: kubernetes, move SD struct from config into kubernetes discovery package
* refactor: notifier, use targetgroup package instead of config
* refactor: tests for file, marathon, triton SD - use targetgroup package instead of config.TargetGroup
* refactor: retrieval, use targetgroup package instead of config.TargetGroup
* refactor: storage, use config util package
* refactor: discovery manager, use targetgroup package instead of config.TargetGroup
* refactor: use HTTPClient and TLS config from configUtil instead of config
* refactor: tests, use targetgroup package instead of config.TargetGroup
* refactor: fix tagetgroup.Group pointers that were removed by mistake
* refactor: openstack, kubernetes: drop prefixes
* refactor: remove import aliases forced due to vscode bug
* refactor: move main SD struct out of config into discovery/config
* refactor: rename configUtil to config_util
* refactor: rename yamlUtil to yaml_config
* refactor: kubernetes, remove prefixes
* refactor: move the TargetGroup package to discovery/
* refactor: fix order of imports
											
										 
											2017-12-30 04:01:34 +08:00
										 |  |  | 	HTTPClientConfig config_util.HTTPClientConfig | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewClient creates a new Client.
 | 
					
						
							| 
									
										
										
										
											2017-10-23 21:53:43 +08:00
										 |  |  | func NewClient(index int, conf *ClientConfig) (*Client, error) { | 
					
						
							| 
									
										
										
										
											2019-08-14 17:00:39 +08:00
										 |  |  | 	httpClient, err := config_util.NewClientFromConfig(conf.HTTPClientConfig, "remote_storage", false) | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &Client{ | 
					
						
							| 
									
										
										
										
											2017-11-12 09:15:27 +08:00
										 |  |  | 		index:   index, | 
					
						
							|  |  |  | 		url:     conf.URL, | 
					
						
							|  |  |  | 		client:  httpClient, | 
					
						
							|  |  |  | 		timeout: time.Duration(conf.Timeout), | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type recoverableError struct { | 
					
						
							|  |  |  | 	error | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-08 05:26:04 +08:00
										 |  |  | // Store sends a batch of samples to the HTTP endpoint, the request is the proto marshalled
 | 
					
						
							|  |  |  | // and encoded bytes from codec.go.
 | 
					
						
							|  |  |  | func (c *Client) Store(ctx context.Context, req []byte) error { | 
					
						
							|  |  |  | 	httpReq, err := http.NewRequest("POST", c.url.String(), bytes.NewReader(req)) | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-09-30 23:54:55 +08:00
										 |  |  | 		// Errors from NewRequest are from unparsable URLs, so are not
 | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 		// recoverable.
 | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	httpReq.Header.Add("Content-Encoding", "snappy") | 
					
						
							|  |  |  | 	httpReq.Header.Set("Content-Type", "application/x-protobuf") | 
					
						
							| 
									
										
										
										
											2018-11-23 22:49:49 +08:00
										 |  |  | 	httpReq.Header.Set("User-Agent", userAgent) | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	httpReq.Header.Set("X-Prometheus-Remote-Write-Version", "0.1.0") | 
					
						
							| 
									
										
										
										
											2018-05-29 16:51:29 +08:00
										 |  |  | 	httpReq = httpReq.WithContext(ctx) | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ctx, cancel := context.WithTimeout(context.Background(), c.timeout) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-19 19:31:16 +08:00
										 |  |  | 	httpResp, err := c.client.Do(httpReq.WithContext(ctx)) | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		// Errors from client.Do are from (for example) network errors, so are
 | 
					
						
							|  |  |  | 		// recoverable.
 | 
					
						
							|  |  |  | 		return recoverableError{err} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-04-18 16:50:37 +08:00
										 |  |  | 	defer func() { | 
					
						
							|  |  |  | 		io.Copy(ioutil.Discard, httpResp.Body) | 
					
						
							|  |  |  | 		httpResp.Body.Close() | 
					
						
							|  |  |  | 	}() | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if httpResp.StatusCode/100 != 2 { | 
					
						
							|  |  |  | 		scanner := bufio.NewScanner(io.LimitReader(httpResp.Body, maxErrMsgLen)) | 
					
						
							|  |  |  | 		line := "" | 
					
						
							|  |  |  | 		if scanner.Scan() { | 
					
						
							|  |  |  | 			line = scanner.Text() | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-03-26 07:01:12 +08:00
										 |  |  | 		err = errors.Errorf("server returned HTTP status %s: %s", httpResp.Status, line) | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if httpResp.StatusCode/100 == 5 { | 
					
						
							|  |  |  | 		return recoverableError{err} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Name identifies the client.
 | 
					
						
							|  |  |  | func (c Client) Name() string { | 
					
						
							|  |  |  | 	return fmt.Sprintf("%d:%s", c.index, c.url) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Read reads from a remote endpoint.
 | 
					
						
							| 
									
										
										
										
											2017-10-24 04:28:17 +08:00
										 |  |  | func (c *Client) Read(ctx context.Context, query *prompb.Query) (*prompb.QueryResult, error) { | 
					
						
							| 
									
										
										
										
											2017-10-23 21:44:57 +08:00
										 |  |  | 	req := &prompb.ReadRequest{ | 
					
						
							| 
									
										
										
										
											2017-10-23 21:53:43 +08:00
										 |  |  | 		// TODO: Support batching multiple queries into one read request,
 | 
					
						
							|  |  |  | 		// as the protobuf interface allows for it.
 | 
					
						
							| 
									
										
										
										
											2017-10-23 21:44:57 +08:00
										 |  |  | 		Queries: []*prompb.Query{ | 
					
						
							|  |  |  | 			query, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	data, err := proto.Marshal(req) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-03-26 07:01:12 +08:00
										 |  |  | 		return nil, errors.Wrapf(err, "unable to marshal read request") | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	compressed := snappy.Encode(nil, data) | 
					
						
							|  |  |  | 	httpReq, err := http.NewRequest("POST", c.url.String(), bytes.NewReader(compressed)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-03-26 07:01:12 +08:00
										 |  |  | 		return nil, errors.Wrap(err, "unable to create request") | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	httpReq.Header.Add("Content-Encoding", "snappy") | 
					
						
							| 
									
										
										
										
											2018-07-26 19:45:04 +08:00
										 |  |  | 	httpReq.Header.Add("Accept-Encoding", "snappy") | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	httpReq.Header.Set("Content-Type", "application/x-protobuf") | 
					
						
							| 
									
										
										
										
											2018-11-23 22:49:49 +08:00
										 |  |  | 	httpReq.Header.Set("User-Agent", userAgent) | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	httpReq.Header.Set("X-Prometheus-Remote-Read-Version", "0.1.0") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ctx, cancel := context.WithTimeout(ctx, c.timeout) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-19 19:31:16 +08:00
										 |  |  | 	httpResp, err := c.client.Do(httpReq.WithContext(ctx)) | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-03-26 07:01:12 +08:00
										 |  |  | 		return nil, errors.Wrap(err, "error sending request") | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-04-18 16:50:37 +08:00
										 |  |  | 	defer func() { | 
					
						
							|  |  |  | 		io.Copy(ioutil.Discard, httpResp.Body) | 
					
						
							|  |  |  | 		httpResp.Body.Close() | 
					
						
							|  |  |  | 	}() | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	compressed, err = ioutil.ReadAll(httpResp.Body) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-08-28 21:25:28 +08:00
										 |  |  | 		return nil, errors.Wrap(err, fmt.Sprintf("error reading response. HTTP status code: %s", httpResp.Status)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if httpResp.StatusCode/100 != 2 { | 
					
						
							|  |  |  | 		return nil, errors.Errorf("remote server %s returned HTTP status %s: %s", c.url.String(), httpResp.Status, strings.TrimSpace(string(compressed))) | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	uncompressed, err := snappy.Decode(nil, compressed) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-03-26 07:01:12 +08:00
										 |  |  | 		return nil, errors.Wrap(err, "error reading response") | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 05:06:35 +08:00
										 |  |  | 	var resp prompb.ReadResponse | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	err = proto.Unmarshal(uncompressed, &resp) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-03-26 07:01:12 +08:00
										 |  |  | 		return nil, errors.Wrap(err, "unable to unmarshal response body") | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(resp.Results) != len(req.Queries) { | 
					
						
							| 
									
										
										
										
											2019-03-26 07:01:12 +08:00
										 |  |  | 		return nil, errors.Errorf("responses: want %d, got %d", len(req.Queries), len(resp.Results)) | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-24 04:28:17 +08:00
										 |  |  | 	return resp.Results[0], nil | 
					
						
							| 
									
										
										
										
											2017-05-10 17:44:13 +08:00
										 |  |  | } |