mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-12 06:17:27 -08:00
Merge branch 'master' into mergemaster
This commit is contained in:
commit
25f3e1c424
|
@ -1,3 +1,7 @@
|
|||
## 1.7.1 / 2017-06-12
|
||||
|
||||
* [BUGFIX] Fix double prefix redirect.
|
||||
|
||||
## 1.7.0 / 2017-06-06
|
||||
|
||||
* [CHANGE] Compress remote storage requests and responses with unframed/raw snappy.
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
Prometheus uses GitHub to manage reviews of pull requests.
|
||||
|
||||
* If you are a new contributor see: [Steps to Contribute](#steps-to-contribute)
|
||||
|
||||
* If you have a trivial fix or improvement, go ahead and create a pull request,
|
||||
addressing (with `@...`) a suitable maintainer of this repository (see
|
||||
[MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request.
|
||||
|
@ -16,3 +18,34 @@ Prometheus uses GitHub to manage reviews of pull requests.
|
|||
and the _Formatting and style_ section of Peter Bourgon's [Go: Best
|
||||
Practices for Production
|
||||
Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style).
|
||||
|
||||
|
||||
## Steps to Contribute
|
||||
|
||||
Should you wish to work on an issue, please claim it first by commenting on the GitHub issue that you want to work on it. This is to prevent duplicated efforts from contributors on the same issue.
|
||||
|
||||
Please check the [`low-hanging-fruit`](https://github.com/prometheus/prometheus/issues?q=is%3Aissue+is%3Aopen+label%3A%22low+hanging+fruit%22) label to find issues that are good for getting started. If you have questions about one of the issues, with or without the tag, please comment on them and one of the maintainers will clarify it. For a quicker response, contact us over [IRC](https://prometheus.io/community).
|
||||
|
||||
For complete instructions on how to compile see: [Building From Source](https://github.com/prometheus/prometheus#building-from-source)
|
||||
|
||||
For quickly compiling and testing your changes do:
|
||||
```
|
||||
# For building.
|
||||
go build ./cmd/prometheus/
|
||||
./prometheus
|
||||
|
||||
# For testing.
|
||||
make test # Make sure all the tests pass before you commit and push :)
|
||||
```
|
||||
|
||||
All our issues are regularly tagged so that you can also filter down the issues involving the components you want to work on. For our labelling policy refer [the wiki page](https://github.com/prometheus/prometheus/wiki/Label-Names-and-Descriptions).
|
||||
|
||||
## Pull Request Checklist
|
||||
|
||||
* Branch from the master branch and, if needed, rebase to the current master branch before submitting your pull request. If it doesn't merge cleanly with master you may be asked to rebase your changes.
|
||||
|
||||
* Commits should be as small as possible, while ensuring that each commit is correct independently (i.e., each commit should compile and pass tests).
|
||||
|
||||
* If your patch is not getting reviewed or you need a specific person to review it, you can @-reply a reviewer asking for a review in the pull request or a comment, or you can ask for a review on IRC channel [#prometheus](https://webchat.freenode.net/?channels=#prometheus) on irc.freenode.net (for the easiest start, [join via Riot](https://riot.im/app/#/room/#prometheus:matrix.org)).
|
||||
|
||||
* Add tests relevant to the fixed bug or new feature.
|
||||
|
|
|
@ -329,6 +329,10 @@ func main() {
|
|||
// Wait for reload or termination signals.
|
||||
close(hupReady) // Unblock SIGHUP handler.
|
||||
|
||||
// Set web server to ready.
|
||||
webHandler.Ready()
|
||||
log.Info("Server is Ready to receive requests.")
|
||||
|
||||
term := make(chan os.Signal)
|
||||
signal.Notify(term, os.Interrupt, syscall.SIGTERM)
|
||||
select {
|
||||
|
|
|
@ -173,6 +173,25 @@ var (
|
|||
// DefaultRemoteWriteConfig is the default remote write configuration.
|
||||
DefaultRemoteWriteConfig = RemoteWriteConfig{
|
||||
RemoteTimeout: model.Duration(30 * time.Second),
|
||||
QueueConfig: DefaultQueueConfig,
|
||||
}
|
||||
|
||||
// DefaultQueueConfig is the default remote queue configuration.
|
||||
DefaultQueueConfig = QueueConfig{
|
||||
// With a maximum of 1000 shards, assuming an average of 100ms remote write
|
||||
// time and 100 samples per batch, we will be able to push 1M samples/s.
|
||||
MaxShards: 1000,
|
||||
MaxSamplesPerSend: 100,
|
||||
|
||||
// By default, buffer 1000 batches, which at 100ms per batch is 1:40mins. At
|
||||
// 1000 shards, this will buffer 100M samples total.
|
||||
Capacity: 100 * 1000,
|
||||
BatchSendDeadline: 5 * time.Second,
|
||||
|
||||
// Max number of times to retry a batch on recoverable errors.
|
||||
MaxRetries: 10,
|
||||
MinBackoff: 30 * time.Millisecond,
|
||||
MaxBackoff: 100 * time.Millisecond,
|
||||
}
|
||||
|
||||
// DefaultRemoteReadConfig is the default remote read configuration.
|
||||
|
@ -1390,13 +1409,14 @@ func (re Regexp) MarshalYAML() (interface{}, error) {
|
|||
|
||||
// RemoteWriteConfig is the configuration for writing to remote storage.
|
||||
type RemoteWriteConfig struct {
|
||||
URL *URL `yaml:"url,omitempty"`
|
||||
URL *URL `yaml:"url"`
|
||||
RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"`
|
||||
WriteRelabelConfigs []*RelabelConfig `yaml:"write_relabel_configs,omitempty"`
|
||||
|
||||
// We cannot do proper Go type embedding below as the parser will then parse
|
||||
// values arbitrarily into the overflow maps of further-down types.
|
||||
HTTPClientConfig HTTPClientConfig `yaml:",inline"`
|
||||
QueueConfig QueueConfig `yaml:"queue_config,omitempty"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
|
@ -1409,15 +1429,49 @@ func (c *RemoteWriteConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
|
|||
if err := unmarshal((*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.URL == nil {
|
||||
return fmt.Errorf("url for remote_write is empty")
|
||||
}
|
||||
|
||||
// The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer.
|
||||
// We cannot make it a pointer as the parser panics for inlined pointer structs.
|
||||
// Thus we just do its validation here.
|
||||
if err := c.HTTPClientConfig.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkOverflow(c.XXX, "remote_write"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QueueConfig is the configuration for the queue used to write to remote
|
||||
// storage.
|
||||
type QueueConfig struct {
|
||||
// Number of samples to buffer per shard before we start dropping them.
|
||||
Capacity int `yaml:"capacity,omitempty"`
|
||||
|
||||
// Max number of shards, i.e. amount of concurrency.
|
||||
MaxShards int `yaml:"max_shards,omitempty"`
|
||||
|
||||
// Maximum number of samples per send.
|
||||
MaxSamplesPerSend int `yaml:"max_samples_per_send,omitempty"`
|
||||
|
||||
// Maximum time sample will wait in buffer.
|
||||
BatchSendDeadline time.Duration `yaml:"batch_send_deadline,omitempty"`
|
||||
|
||||
// Max number of times to retry a batch on recoverable errors.
|
||||
MaxRetries int `yaml:"max_retries,omitempty"`
|
||||
|
||||
// On recoverable errors, backoff exponentially.
|
||||
MinBackoff time.Duration `yaml:"min_backoff,omitempty"`
|
||||
MaxBackoff time.Duration `yaml:"max_backoff,omitempty"`
|
||||
}
|
||||
|
||||
// RemoteReadConfig is the configuration for reading from remote storage.
|
||||
type RemoteReadConfig struct {
|
||||
URL *URL `yaml:"url,omitempty"`
|
||||
URL *URL `yaml:"url"`
|
||||
RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"`
|
||||
|
||||
// We cannot do proper Go type embedding below as the parser will then parse
|
||||
|
@ -1435,6 +1489,17 @@ func (c *RemoteReadConfig) UnmarshalYAML(unmarshal func(interface{}) error) erro
|
|||
if err := unmarshal((*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.URL == nil {
|
||||
return fmt.Errorf("url for remote_read is empty")
|
||||
}
|
||||
|
||||
// The UnmarshalYAML method of HTTPClientConfig is not being called because it's not a pointer.
|
||||
// We cannot make it a pointer as the parser panics for inlined pointer structs.
|
||||
// Thus we just do its validation here.
|
||||
if err := c.HTTPClientConfig.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkOverflow(c.XXX, "remote_read"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -66,10 +66,12 @@ var expectedConf = &Config{
|
|||
Action: RelabelDrop,
|
||||
},
|
||||
},
|
||||
QueueConfig: DefaultQueueConfig,
|
||||
},
|
||||
{
|
||||
URL: mustParseURL("http://remote2/push"),
|
||||
RemoteTimeout: model.Duration(30 * time.Second),
|
||||
QueueConfig: DefaultQueueConfig,
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -666,6 +668,12 @@ var expectedErrors = []struct {
|
|||
}, {
|
||||
filename: "unknown_global_attr.bad.yml",
|
||||
errMsg: "unknown fields in global config: nonexistent_field",
|
||||
}, {
|
||||
filename: "remote_read_url_missing.bad.yml",
|
||||
errMsg: `url for remote_read is empty`,
|
||||
}, {
|
||||
filename: "remote_write_url_missing.bad.yml",
|
||||
errMsg: `url for remote_write is empty`,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
2
config/testdata/remote_read_url_missing.bad.yml
vendored
Normal file
2
config/testdata/remote_read_url_missing.bad.yml
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
remote_read:
|
||||
- url:
|
2
config/testdata/remote_write_url_missing.bad.yml
vendored
Normal file
2
config/testdata/remote_write_url_missing.bad.yml
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
remote_write:
|
||||
- url:
|
215
discovery/README.md
Normal file
215
discovery/README.md
Normal file
|
@ -0,0 +1,215 @@
|
|||
### Service Discovery
|
||||
|
||||
This directory contains the service discovery (SD) component of Prometheus.
|
||||
|
||||
|
||||
|
||||
## Design of a Prometheus SD
|
||||
|
||||
There are many requests to add new SDs to Prometheus, this section looks at
|
||||
what makes a good SD and covers some of the common implementation issues.
|
||||
|
||||
### Does this make sense as an SD?
|
||||
|
||||
The first question to be asked is does it make sense to add this particular
|
||||
SD? An SD mechanism should be reasonably well established, and at a minimum in
|
||||
use across multiple organisations. It should allow discovering of machines
|
||||
and/or services running somewhere. When exactly an SD is popular enough to
|
||||
justify being added to Prometheus natively is an open question.
|
||||
|
||||
It should not be a brand new SD mechanism, or a variant of an established
|
||||
mechanism. We want to integrate Prometheus with the SD that's already there in
|
||||
your infrastructure, not invent yet more ways to do service discovery. We also
|
||||
do not add mechanisms to work around users lacking service discovery and/or
|
||||
configuration management infrastructure.
|
||||
|
||||
SDs that merely discover other applications running the same software (e.g.
|
||||
talk to one Kafka or Cassandra server to find the others) are not service
|
||||
discovery. In that case the SD you should be looking at is whatever decides
|
||||
that a machine is going to be a Kafka server, likely a machine database or
|
||||
configuration management system.
|
||||
|
||||
If something is particularly custom or unusual, `file_sd` is the generic
|
||||
mechanism provided for users to hook in. Generally with Prometheus we offer a
|
||||
single generic mechanism for things with infinite variations, rather than
|
||||
trying to support everything natively (see also, alertmanager webhook, remote
|
||||
read, remote write, node exporter textfile collector). For example anything
|
||||
that would involve talking to a relational database should use `file_sd`
|
||||
instead.
|
||||
|
||||
For configuration management systems like Chef, while they do have a
|
||||
database/API that'd in principle make sense to talk to for service discovery,
|
||||
the idiomatic approach is to use Chef's templating facilities to write out a
|
||||
file for use with `file_sd`.
|
||||
|
||||
|
||||
### Mapping from SD to Prometheus
|
||||
|
||||
The general principle with SD is to extract all the potentially useful
|
||||
information we can out of the SD, and let the user choose what they need of it
|
||||
using
|
||||
[relabelling](https://prometheus.io/docs/operating/configuration/#<relabel_config>).
|
||||
This information is generally termed metadata.
|
||||
|
||||
Metadata is exposed as a set of key/value pairs (labels) per target. The keys
|
||||
are prefixed with `__meta_<sdname>_<key>`, and there should also be an `__address__`
|
||||
label with the host:port of the target (preferably an IP address to avoid DNS
|
||||
lookups). No other labelnames should be exposed.
|
||||
|
||||
It is very common for initial pull requests for new SDs to include hardcoded
|
||||
assumptions that make sense for the the author's setup. SD should be generic,
|
||||
any customisation should be handled via relabelling. There should be basically
|
||||
no business logic, filtering, or transformations of the data from the SD beyond
|
||||
that which is needed to fit it into the metadata data model.
|
||||
|
||||
Arrays (e.g. a list of tags) should be converted to a single label with the
|
||||
array values joined with a comma. Also prefix and suffix the value with a
|
||||
comma. So for example the array `[a, b, c]` would become `,a,b,c,`. As
|
||||
relabelling regexes are fully anchored, this makes it easier to write correct
|
||||
regexes against (`.*,a,.*` works no matter where `a` appears in the list). The
|
||||
canonical example of this is `__meta_consul_tags`.
|
||||
|
||||
Maps, hashes and other forms of key/value pairs should be all prefixed and
|
||||
exposed as labels. For example for EC2 tags, there would be
|
||||
`__meta_ec2_tag_Description=mydescription` for the Description tag. Labelnames
|
||||
may only contain `[_a-zA-Z0-9]`, sanitize by replacing with underscores as needed.
|
||||
|
||||
For targets with multiple potential ports, you can a) expose them as a list, b)
|
||||
if they're named expose them as a map or c) expose them each as their own
|
||||
target. Kubernetes SD takes the target per port approach. a) and b) can be
|
||||
combined.
|
||||
|
||||
For machine-like SDs (OpenStack, EC2, Kubernetes to some extent) there may
|
||||
be multiple network interfaces for a target. Thus far reporting the details
|
||||
of only the first/primary network interface has sufficed.
|
||||
|
||||
|
||||
### Other implementation considerations
|
||||
|
||||
SDs are intended to dump all possible targets. For example the optional use of
|
||||
EC2 service discovery would be to take the entire region's worth of EC2
|
||||
instances it provides and do everything needed in one `scrape_config`. For
|
||||
large deployments where you are only interested in a small proportion of the
|
||||
returned targets, this may cause performance issues. If this occurs it is
|
||||
acceptable to also offer filtering via whatever mechanisms the SD exposes. For
|
||||
EC2 that would be the `Filter` option on `DescribeInstances`. Keep in mind that
|
||||
this is a performance optimisation, it should be possible to do the same
|
||||
filtering using relabelling alone. As with SD generally, we do not invent new
|
||||
ways to filter targets (that is what relabelling is for), merely offer up
|
||||
whatever functionality the SD itself offers.
|
||||
|
||||
It is a general rule with Prometheus that all configuration comes from the
|
||||
configuration file. While the libraries you use to talk to the SD may also
|
||||
offer other mechanisms for providing configuration/authentication under the
|
||||
covers (EC2's use of environment variables being a prime example), using your SD
|
||||
mechanism should not require this. Put another way, your SD implementation
|
||||
should not read environment variables or files to obtain configuration.
|
||||
|
||||
Some SD mechanisms have rate limits that make them challenging to use. As an
|
||||
example we have unfortunately had to reject Amazon ECS service discovery due to
|
||||
the rate limits being so low that it would not be usable for anything beyond
|
||||
small setups.
|
||||
|
||||
If a system offers multiple distinct types of SD, select which is in use with a
|
||||
configuration option rather than returning them all from one mega SD that
|
||||
requires relabelling to select just the one you want. So far we have only seen
|
||||
this with Kubernetes. When a single SD with a selector vs. multiple distinct
|
||||
SDs makes sense is an open question.
|
||||
|
||||
If there is a failure while processing talking to the SD, abort rather than
|
||||
returning partial data. It is better to work from stale targets than partial
|
||||
or incorrect metadata.
|
||||
|
||||
The information obtained from service discovery is not considered sensitive
|
||||
security wise. Do not return secrets in metadata, anyone with access to
|
||||
the Prometheus server will be able to see them.
|
||||
|
||||
|
||||
## Writing an SD mechanism
|
||||
|
||||
### The SD interface
|
||||
|
||||
A Service Discovery (SD) mechanism has to discover targets and provide them to Prometheus. We expect similar targets to be grouped together, in the form of a [`TargetGroup`](https://godoc.org/github.com/prometheus/prometheus/config#TargetGroup). The SD mechanism sends the targets down to prometheus as list of `TargetGroups`.
|
||||
|
||||
An SD mechanism has to implement the `TargetProvider` Interface:
|
||||
```go
|
||||
type TargetProvider interface {
|
||||
Run(ctx context.Context, up chan<- []*config.TargetGroup)
|
||||
}
|
||||
```
|
||||
|
||||
Prometheus will call the `Run()` method on a provider to initialise the discovery mechanism. The mechanism will then send *all* the `TargetGroup`s into the channel. Now the mechanism will watch for changes and then send only changed and new `TargetGroup`s down the channel.
|
||||
|
||||
For example if we had a discovery mechanism and it retrieves the following groups:
|
||||
|
||||
```
|
||||
[]config.TargetGroup{
|
||||
{
|
||||
Targets: []model.LabelSet{
|
||||
{
|
||||
"__instance__": "10.11.150.1:7870",
|
||||
"hostname": "demo-target-1",
|
||||
"test": "simple-test",
|
||||
},
|
||||
{
|
||||
"__instance__": "10.11.150.4:7870",
|
||||
"hostname": "demo-target-2",
|
||||
"test": "simple-test",
|
||||
},
|
||||
},
|
||||
Labels: map[LabelName][LabelValue] {
|
||||
"job": "mysql",
|
||||
},
|
||||
"Source": "file1",
|
||||
},
|
||||
{
|
||||
Targets: []model.LabelSet{
|
||||
{
|
||||
"__instance__": "10.11.122.11:6001",
|
||||
"hostname": "demo-postgres-1",
|
||||
"test": "simple-test",
|
||||
},
|
||||
{
|
||||
"__instance__": "10.11.122.15:6001",
|
||||
"hostname": "demo-postgres-2",
|
||||
"test": "simple-test",
|
||||
},
|
||||
},
|
||||
Labels: map[LabelName][LabelValue] {
|
||||
"job": "postgres",
|
||||
},
|
||||
"Source": "file2",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Here there are two `TargetGroups` one group with source `file1` and another with `file2`. The grouping is implementation specific and could even be one target per group. But, one has to make sure every target group sent by an SD instance should have a `Source` which is unique across all the `TargetGroup`s of that SD instance.
|
||||
|
||||
In this case, both the `TargetGroup`s are sent down the channel the first time `Run()` is called. Now, for an update, we need to send the whole _changed_ `TargetGroup` down the channel. i.e, if the target with `hostname: demo-postgres-2` goes away, we send:
|
||||
```
|
||||
&config.TargetGroup{
|
||||
Targets: []model.LabelSet{
|
||||
{
|
||||
"__instance__": "10.11.122.11:6001",
|
||||
"hostname": "demo-postgres-1",
|
||||
"test": "simple-test",
|
||||
},
|
||||
},
|
||||
Labels: map[LabelName][LabelValue] {
|
||||
"job": "postgres",
|
||||
},
|
||||
"Source": "file2",
|
||||
}
|
||||
```
|
||||
down the channel.
|
||||
|
||||
If all the targets in a group go away, we need to send the target groups with empty `Targets` down the channel. i.e, if all targets with `job: postgres` go away, we send:
|
||||
```
|
||||
&config.TargetGroup{
|
||||
Targets: nil,
|
||||
"Source": "file2",
|
||||
}
|
||||
```
|
||||
down the channel.
|
||||
|
||||
<!-- TODO: Add best-practices -->
|
|
@ -4,6 +4,9 @@
|
|||
#
|
||||
# Kubernetes labels will be added as Prometheus labels on metrics via the
|
||||
# `labelmap` relabeling action.
|
||||
#
|
||||
# If you are using Kubernetes 1.7.2 or earlier, please take note of the comments
|
||||
# for the kubernetes-cadvisor job; you will need to edit or remove this job.
|
||||
|
||||
# Scrape config for API servers.
|
||||
#
|
||||
|
@ -47,6 +50,12 @@ scrape_configs:
|
|||
action: keep
|
||||
regex: default;kubernetes;https
|
||||
|
||||
# Scrape config for nodes (kubelet).
|
||||
#
|
||||
# Rather than connecting directly to the node, the scrape is proxied though the
|
||||
# Kubernetes apiserver. This means it will work if Prometheus is running out of
|
||||
# cluster, or can't connect to nodes for some other reason (e.g. because of
|
||||
# firewalling).
|
||||
- job_name: 'kubernetes-nodes'
|
||||
|
||||
# Default to scraping over https. If required, just disable this or change to
|
||||
|
@ -61,13 +70,6 @@ scrape_configs:
|
|||
# <kubernetes_sd_config>.
|
||||
tls_config:
|
||||
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
# If your node certificates are self-signed or use a different CA to the
|
||||
# master CA, then disable certificate verification below. Note that
|
||||
# certificate verification is an integral part of a secure infrastructure
|
||||
# so this should only be disabled in a controlled environment. You can
|
||||
# disable certificate verification by uncommenting the line below.
|
||||
#
|
||||
# insecure_skip_verify: true
|
||||
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
|
||||
kubernetes_sd_configs:
|
||||
|
@ -83,6 +85,49 @@ scrape_configs:
|
|||
target_label: __metrics_path__
|
||||
replacement: /api/v1/nodes/${1}/proxy/metrics
|
||||
|
||||
# Scrape config for Kubelet cAdvisor.
|
||||
#
|
||||
# This is required for Kubernetes 1.7.3 and later, where cAdvisor metrics
|
||||
# (those whose names begin with 'container_') have been removed from the
|
||||
# Kubelet metrics endpoint. This job scrapes the cAdvisor endpoint to
|
||||
# retrieve those metrics.
|
||||
#
|
||||
# In Kubernetes 1.7.0-1.7.2, these metrics are only exposed on the cAdvisor
|
||||
# HTTP endpoint; use "replacement: /api/v1/nodes/${1}:4194/proxy/metrics"
|
||||
# in that case (and ensure cAdvisor's HTTP server hasn't been disabled with
|
||||
# the --cadvisor-port=0 Kubelet flag).
|
||||
#
|
||||
# This job is not necessary and should be removed in Kubernetes 1.6 and
|
||||
# earlier versions, or it will cause the metrics to be scraped twice.
|
||||
- job_name: 'kubernetes-cadvisor'
|
||||
|
||||
# Default to scraping over https. If required, just disable this or change to
|
||||
# `http`.
|
||||
scheme: https
|
||||
|
||||
# This TLS & bearer token file config is used to connect to the actual scrape
|
||||
# endpoints for cluster components. This is separate to discovery auth
|
||||
# configuration because discovery & scraping are two separate concerns in
|
||||
# Prometheus. The discovery auth config is automatic if Prometheus runs inside
|
||||
# the cluster. Otherwise, more config options have to be provided within the
|
||||
# <kubernetes_sd_config>.
|
||||
tls_config:
|
||||
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
|
||||
kubernetes_sd_configs:
|
||||
- role: node
|
||||
|
||||
relabel_configs:
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_node_label_(.+)
|
||||
- target_label: __address__
|
||||
replacement: kubernetes.default.svc:443
|
||||
- source_labels: [__meta_kubernetes_node_name]
|
||||
regex: (.+)
|
||||
target_label: __metrics_path__
|
||||
replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
|
||||
|
||||
# Scrape config for service endpoints.
|
||||
#
|
||||
# The relabeling allows the actual service scrape endpoint to be configured
|
||||
|
|
|
@ -145,7 +145,7 @@ type query struct {
|
|||
stmt Statement
|
||||
// Timer stats for the query execution.
|
||||
stats *stats.TimerGroup
|
||||
// Cancelation function for the query.
|
||||
// Cancellation function for the query.
|
||||
cancel func()
|
||||
|
||||
// The engine against which the query is executed.
|
||||
|
@ -669,7 +669,7 @@ func (ev *evaluator) Eval(expr Expr) (v Value, err error) {
|
|||
// eval evaluates the given expression as the given AST expression node requires.
|
||||
func (ev *evaluator) eval(expr Expr) Value {
|
||||
// This is the top-level evaluation method.
|
||||
// Thus, we check for timeout/cancelation here.
|
||||
// Thus, we check for timeout/cancellation here.
|
||||
if err := contextDone(ev.ctx, "expression evaluation"); err != nil {
|
||||
ev.error(err)
|
||||
}
|
||||
|
|
|
@ -194,7 +194,7 @@ func TestEngineShutdown(t *testing.T) {
|
|||
t.Fatalf("expected error on querying with canceled context but got none")
|
||||
}
|
||||
if _, ok := res2.Err.(ErrQueryCanceled); !ok {
|
||||
t.Fatalf("expected cancelation error, got %q", res2.Err)
|
||||
t.Fatalf("expected cancellation error, got %q", res2.Err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -699,7 +699,11 @@ func funcDeriv(ev *evaluator, args Expressions) Value {
|
|||
if len(samples.Points) < 2 {
|
||||
continue
|
||||
}
|
||||
slope, _ := linearRegression(samples.Points, 0)
|
||||
|
||||
// We pass in an arbitrary timestamp that is near the values in use
|
||||
// to avoid floating point accuracy issues, see
|
||||
// https://github.com/prometheus/prometheus/issues/2674
|
||||
slope, _ := linearRegression(samples.Points, samples.Points[0].T)
|
||||
resultSample := Sample{
|
||||
Metric: dropMetricName(samples.Metric),
|
||||
Point: Point{V: slope, T: ev.Timestamp},
|
||||
|
|
|
@ -13,7 +13,14 @@
|
|||
|
||||
package promql
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/prometheus/pkg/labels"
|
||||
"github.com/prometheus/prometheus/pkg/timestamp"
|
||||
"github.com/prometheus/prometheus/util/testutil"
|
||||
)
|
||||
|
||||
func BenchmarkHoltWinters4Week5Min(b *testing.B) {
|
||||
input := `
|
||||
|
@ -71,3 +78,41 @@ eval instant at 1d changes(http_requests[1d])
|
|||
bench := NewBenchmark(b, input)
|
||||
bench.Run()
|
||||
}
|
||||
|
||||
func TestDeriv(t *testing.T) {
|
||||
// https://github.com/prometheus/prometheus/issues/2674#issuecomment-315439393
|
||||
// This requires more precision than the usual test system offers,
|
||||
// so we test it by hand.
|
||||
storage := testutil.NewStorage(t)
|
||||
defer storage.Close()
|
||||
engine := NewEngine(storage, nil)
|
||||
|
||||
a, err := storage.Appender()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
metric := labels.FromStrings("__name__", "foo")
|
||||
a.Add(metric, 1493712816939, 1.0)
|
||||
a.Add(metric, 1493712846939, 1.0)
|
||||
|
||||
if err := a.Commit(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
query, err := engine.NewInstantQuery("deriv(foo[30m])", timestamp.Time(1493712846939))
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing query: %s", err)
|
||||
}
|
||||
result := query.Exec(context.Background())
|
||||
if result.Err != nil {
|
||||
t.Fatalf("Error running query: %s", result.Err)
|
||||
}
|
||||
vec, _ := result.Vector()
|
||||
if len(vec) != 1 {
|
||||
t.Fatalf("Expected 1 result, got %d", len(vec))
|
||||
}
|
||||
if vec[0].V != 0.0 {
|
||||
t.Fatalf("Expected 0.0 as value, got %f", vec[0].V)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,8 @@ import (
|
|||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
)
|
||||
|
@ -72,33 +69,6 @@ func NewClientFromConfig(cfg config.HTTPClientConfig) (*http.Client, error) {
|
|||
return NewClient(rt), nil
|
||||
}
|
||||
|
||||
// NewDeadlineRoundTripper returns a new http.RoundTripper which will time out
|
||||
// long running requests.
|
||||
func NewDeadlineRoundTripper(timeout time.Duration, proxyURL *url.URL) http.RoundTripper {
|
||||
return &http.Transport{
|
||||
// Set proxy (if null, then becomes a direct connection)
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
// We need to disable keepalive, because we set a deadline on the
|
||||
// underlying connection.
|
||||
DisableKeepAlives: true,
|
||||
Dial: func(netw, addr string) (c net.Conn, err error) {
|
||||
start := time.Now()
|
||||
|
||||
c, err = net.DialTimeout(netw, addr, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = c.SetDeadline(start.Add(timeout)); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type bearerAuthRoundTripper struct {
|
||||
bearerToken string
|
||||
rt http.RoundTripper
|
||||
|
|
412
util/httputil/client_test.go
Normal file
412
util/httputil/client_test.go
Normal file
|
@ -0,0 +1,412 @@
|
|||
// Copyright 2017 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 httputil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/util/testutil"
|
||||
)
|
||||
|
||||
const (
|
||||
TLSCAChainPath = "testdata/tls-ca-chain.pem"
|
||||
ServerCertificatePath = "testdata/server.crt"
|
||||
ServerKeyPath = "testdata/server.key"
|
||||
BarneyCertificatePath = "testdata/barney.crt"
|
||||
BarneyKeyNoPassPath = "testdata/barney-no-pass.key"
|
||||
MissingCA = "missing/ca.crt"
|
||||
MissingCert = "missing/cert.crt"
|
||||
MissingKey = "missing/secret.key"
|
||||
|
||||
ExpectedMessage = "I'm here to serve you!!!"
|
||||
BearerToken = "theanswertothegreatquestionoflifetheuniverseandeverythingisfortytwo"
|
||||
BearerTokenFile = "testdata/bearer.token"
|
||||
MissingBearerTokenFile = "missing/bearer.token"
|
||||
ExpectedBearer = "Bearer " + BearerToken
|
||||
ExpectedUsername = "arthurdent"
|
||||
ExpectedPassword = "42"
|
||||
)
|
||||
|
||||
func newTestServer(handler func(w http.ResponseWriter, r *http.Request)) (*httptest.Server, error) {
|
||||
testServer := httptest.NewUnstartedServer(http.HandlerFunc(handler))
|
||||
|
||||
tlsCAChain, err := ioutil.ReadFile(TLSCAChainPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Can't read %s", TLSCAChainPath)
|
||||
}
|
||||
serverCertificate, err := tls.LoadX509KeyPair(ServerCertificatePath, ServerKeyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Can't load X509 key pair %s - %s", ServerCertificatePath, ServerKeyPath)
|
||||
}
|
||||
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(tlsCAChain)
|
||||
|
||||
testServer.TLS = &tls.Config{
|
||||
Certificates: make([]tls.Certificate, 1),
|
||||
RootCAs: rootCAs,
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
ClientCAs: rootCAs}
|
||||
testServer.TLS.Certificates[0] = serverCertificate
|
||||
testServer.TLS.BuildNameToCertificate()
|
||||
|
||||
testServer.StartTLS()
|
||||
|
||||
return testServer, nil
|
||||
}
|
||||
|
||||
func TestNewClientFromConfig(t *testing.T) {
|
||||
var newClientValidConfig = []struct {
|
||||
clientConfig config.HTTPClientConfig
|
||||
handler func(w http.ResponseWriter, r *http.Request)
|
||||
}{
|
||||
{
|
||||
clientConfig: config.HTTPClientConfig{
|
||||
TLSConfig: config.TLSConfig{
|
||||
CAFile: "",
|
||||
CertFile: BarneyCertificatePath,
|
||||
KeyFile: BarneyKeyNoPassPath,
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: true},
|
||||
},
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, ExpectedMessage)
|
||||
},
|
||||
}, {
|
||||
clientConfig: config.HTTPClientConfig{
|
||||
TLSConfig: config.TLSConfig{
|
||||
CAFile: TLSCAChainPath,
|
||||
CertFile: BarneyCertificatePath,
|
||||
KeyFile: BarneyKeyNoPassPath,
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: false},
|
||||
},
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, ExpectedMessage)
|
||||
},
|
||||
}, {
|
||||
clientConfig: config.HTTPClientConfig{
|
||||
BearerToken: BearerToken,
|
||||
TLSConfig: config.TLSConfig{
|
||||
CAFile: TLSCAChainPath,
|
||||
CertFile: BarneyCertificatePath,
|
||||
KeyFile: BarneyKeyNoPassPath,
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: false},
|
||||
},
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
bearer := r.Header.Get("Authorization")
|
||||
if bearer != ExpectedBearer {
|
||||
fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
|
||||
ExpectedBearer, bearer)
|
||||
} else {
|
||||
fmt.Fprint(w, ExpectedMessage)
|
||||
}
|
||||
},
|
||||
}, {
|
||||
clientConfig: config.HTTPClientConfig{
|
||||
BearerTokenFile: BearerTokenFile,
|
||||
TLSConfig: config.TLSConfig{
|
||||
CAFile: TLSCAChainPath,
|
||||
CertFile: BarneyCertificatePath,
|
||||
KeyFile: BarneyKeyNoPassPath,
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: false},
|
||||
},
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
bearer := r.Header.Get("Authorization")
|
||||
if bearer != ExpectedBearer {
|
||||
fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
|
||||
ExpectedBearer, bearer)
|
||||
} else {
|
||||
fmt.Fprint(w, ExpectedMessage)
|
||||
}
|
||||
},
|
||||
}, {
|
||||
clientConfig: config.HTTPClientConfig{
|
||||
BasicAuth: &config.BasicAuth{
|
||||
Username: ExpectedUsername,
|
||||
Password: ExpectedPassword,
|
||||
},
|
||||
TLSConfig: config.TLSConfig{
|
||||
CAFile: TLSCAChainPath,
|
||||
CertFile: BarneyCertificatePath,
|
||||
KeyFile: BarneyKeyNoPassPath,
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: false},
|
||||
},
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
username, password, ok := r.BasicAuth()
|
||||
if ok == false {
|
||||
fmt.Fprintf(w, "The Authorization header wasn't set")
|
||||
} else if ExpectedUsername != username {
|
||||
fmt.Fprintf(w, "The expected username (%s) differs from the obtained username (%s).", ExpectedUsername, username)
|
||||
} else if ExpectedPassword != password {
|
||||
fmt.Fprintf(w, "The expected password (%s) differs from the obtained password (%s).", ExpectedPassword, password)
|
||||
} else {
|
||||
fmt.Fprint(w, ExpectedMessage)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, validConfig := range newClientValidConfig {
|
||||
testServer, err := newTestServer(validConfig.handler)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
defer testServer.Close()
|
||||
|
||||
client, err := NewClientFromConfig(validConfig.clientConfig)
|
||||
if err != nil {
|
||||
t.Errorf("Can't create a client from this config: %+v", validConfig.clientConfig)
|
||||
continue
|
||||
}
|
||||
response, err := client.Get(testServer.URL)
|
||||
if err != nil {
|
||||
t.Errorf("Can't connect to the test server using this config: %+v", validConfig.clientConfig)
|
||||
continue
|
||||
}
|
||||
|
||||
message, err := ioutil.ReadAll(response.Body)
|
||||
response.Body.Close()
|
||||
if err != nil {
|
||||
t.Errorf("Can't read the server response body using this config: %+v", validConfig.clientConfig)
|
||||
continue
|
||||
}
|
||||
|
||||
trimMessage := strings.TrimSpace(string(message))
|
||||
if ExpectedMessage != trimMessage {
|
||||
t.Errorf("The expected message (%s) differs from the obtained message (%s) using this config: %+v",
|
||||
ExpectedMessage, trimMessage, validConfig.clientConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClientFromInvalidConfig(t *testing.T) {
|
||||
var newClientInvalidConfig = []struct {
|
||||
clientConfig config.HTTPClientConfig
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
clientConfig: config.HTTPClientConfig{
|
||||
TLSConfig: config.TLSConfig{
|
||||
CAFile: MissingCA,
|
||||
CertFile: "",
|
||||
KeyFile: "",
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: true},
|
||||
},
|
||||
errorMsg: fmt.Sprintf("unable to use specified CA cert %s:", MissingCA),
|
||||
}, {
|
||||
clientConfig: config.HTTPClientConfig{
|
||||
BearerTokenFile: MissingBearerTokenFile,
|
||||
TLSConfig: config.TLSConfig{
|
||||
CAFile: TLSCAChainPath,
|
||||
CertFile: BarneyCertificatePath,
|
||||
KeyFile: BarneyKeyNoPassPath,
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: false},
|
||||
},
|
||||
errorMsg: fmt.Sprintf("unable to read bearer token file %s:", MissingBearerTokenFile),
|
||||
},
|
||||
}
|
||||
|
||||
for _, invalidConfig := range newClientInvalidConfig {
|
||||
client, err := NewClientFromConfig(invalidConfig.clientConfig)
|
||||
if client != nil {
|
||||
t.Errorf("A client instance was returned instead of nil using this config: %+v", invalidConfig.clientConfig)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("No error was returned using this config: %+v", invalidConfig.clientConfig)
|
||||
}
|
||||
if !strings.Contains(err.Error(), invalidConfig.errorMsg) {
|
||||
t.Errorf("Expected error %s does not contain %s", err.Error(), invalidConfig.errorMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBearerAuthRoundTripper(t *testing.T) {
|
||||
const (
|
||||
newBearerToken = "goodbyeandthankyouforthefish"
|
||||
)
|
||||
|
||||
fakeRoundTripper := testutil.NewRoundTripCheckRequest(func(req *http.Request) {
|
||||
bearer := req.Header.Get("Authorization")
|
||||
if bearer != ExpectedBearer {
|
||||
t.Errorf("The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
|
||||
ExpectedBearer, bearer)
|
||||
}
|
||||
}, nil, nil)
|
||||
|
||||
//Normal flow
|
||||
bearerAuthRoundTripper := NewBearerAuthRoundTripper(BearerToken, fakeRoundTripper)
|
||||
request, _ := http.NewRequest("GET", "/hitchhiker", nil)
|
||||
request.Header.Set("User-Agent", "Douglas Adams mind")
|
||||
bearerAuthRoundTripper.RoundTrip(request)
|
||||
|
||||
//Should honor already Authorization header set
|
||||
bearerAuthRoundTripperShouldNotModifyExistingAuthorization := NewBearerAuthRoundTripper(newBearerToken, fakeRoundTripper)
|
||||
request, _ = http.NewRequest("GET", "/hitchhiker", nil)
|
||||
request.Header.Set("Authorization", ExpectedBearer)
|
||||
bearerAuthRoundTripperShouldNotModifyExistingAuthorization.RoundTrip(request)
|
||||
}
|
||||
|
||||
func TestBasicAuthRoundTripper(t *testing.T) {
|
||||
const (
|
||||
newUsername = "fordprefect"
|
||||
newPassword = "towel"
|
||||
)
|
||||
|
||||
fakeRoundTripper := testutil.NewRoundTripCheckRequest(func(req *http.Request) {
|
||||
username, password, ok := req.BasicAuth()
|
||||
if ok == false {
|
||||
t.Errorf("The Authorization header wasn't set")
|
||||
}
|
||||
if ExpectedUsername != username {
|
||||
t.Errorf("The expected username (%s) differs from the obtained username (%s).", ExpectedUsername, username)
|
||||
}
|
||||
if ExpectedPassword != password {
|
||||
t.Errorf("The expected password (%s) differs from the obtained password (%s).", ExpectedPassword, password)
|
||||
}
|
||||
}, nil, nil)
|
||||
|
||||
//Normal flow
|
||||
basicAuthRoundTripper := NewBasicAuthRoundTripper(ExpectedUsername,
|
||||
ExpectedPassword, fakeRoundTripper)
|
||||
request, _ := http.NewRequest("GET", "/hitchhiker", nil)
|
||||
request.Header.Set("User-Agent", "Douglas Adams mind")
|
||||
basicAuthRoundTripper.RoundTrip(request)
|
||||
|
||||
//Should honor already Authorization header set
|
||||
basicAuthRoundTripperShouldNotModifyExistingAuthorization := NewBasicAuthRoundTripper(newUsername,
|
||||
newPassword, fakeRoundTripper)
|
||||
request, _ = http.NewRequest("GET", "/hitchhiker", nil)
|
||||
request.SetBasicAuth(ExpectedUsername, ExpectedPassword)
|
||||
basicAuthRoundTripperShouldNotModifyExistingAuthorization.RoundTrip(request)
|
||||
}
|
||||
|
||||
func TestTLSConfig(t *testing.T) {
|
||||
configTLSConfig := config.TLSConfig{
|
||||
CAFile: TLSCAChainPath,
|
||||
CertFile: BarneyCertificatePath,
|
||||
KeyFile: BarneyKeyNoPassPath,
|
||||
ServerName: "localhost",
|
||||
InsecureSkipVerify: false}
|
||||
|
||||
tlsCAChain, err := ioutil.ReadFile(TLSCAChainPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't read the CA certificate chain (%s)",
|
||||
TLSCAChainPath)
|
||||
}
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(tlsCAChain)
|
||||
|
||||
barneyCertificate, err := tls.LoadX509KeyPair(BarneyCertificatePath, BarneyKeyNoPassPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't load the client key pair ('%s' and '%s'). Reason: %s",
|
||||
BarneyCertificatePath, BarneyKeyNoPassPath, err)
|
||||
}
|
||||
|
||||
expectedTLSConfig := &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
Certificates: []tls.Certificate{barneyCertificate},
|
||||
ServerName: configTLSConfig.ServerName,
|
||||
InsecureSkipVerify: configTLSConfig.InsecureSkipVerify}
|
||||
expectedTLSConfig.BuildNameToCertificate()
|
||||
|
||||
tlsConfig, err := NewTLSConfig(configTLSConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create a new TLS Config from a configuration (%s).", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tlsConfig, expectedTLSConfig) {
|
||||
t.Fatalf("Unexpected TLS Config result: \n\n%+v\n expected\n\n%+v", tlsConfig, expectedTLSConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSConfigEmpty(t *testing.T) {
|
||||
configTLSConfig := config.TLSConfig{
|
||||
CAFile: "",
|
||||
CertFile: "",
|
||||
KeyFile: "",
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: true}
|
||||
|
||||
expectedTLSConfig := &tls.Config{
|
||||
InsecureSkipVerify: configTLSConfig.InsecureSkipVerify}
|
||||
expectedTLSConfig.BuildNameToCertificate()
|
||||
|
||||
tlsConfig, err := NewTLSConfig(configTLSConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create a new TLS Config from a configuration (%s).", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tlsConfig, expectedTLSConfig) {
|
||||
t.Fatalf("Unexpected TLS Config result: \n\n%+v\n expected\n\n%+v", tlsConfig, expectedTLSConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSConfigInvalidCA(t *testing.T) {
|
||||
var invalidTLSConfig = []struct {
|
||||
configTLSConfig config.TLSConfig
|
||||
errorMessage string
|
||||
}{
|
||||
{
|
||||
configTLSConfig: config.TLSConfig{
|
||||
CAFile: MissingCA,
|
||||
CertFile: "",
|
||||
KeyFile: "",
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: false},
|
||||
errorMessage: fmt.Sprintf("unable to use specified CA cert %s:", MissingCA),
|
||||
}, {
|
||||
configTLSConfig: config.TLSConfig{
|
||||
CAFile: "",
|
||||
CertFile: MissingCert,
|
||||
KeyFile: BarneyKeyNoPassPath,
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: false},
|
||||
errorMessage: fmt.Sprintf("unable to use specified client cert (%s) & key (%s):", MissingCert, BarneyKeyNoPassPath),
|
||||
}, {
|
||||
configTLSConfig: config.TLSConfig{
|
||||
CAFile: "",
|
||||
CertFile: BarneyCertificatePath,
|
||||
KeyFile: MissingKey,
|
||||
ServerName: "",
|
||||
InsecureSkipVerify: false},
|
||||
errorMessage: fmt.Sprintf("unable to use specified client cert (%s) & key (%s):", BarneyCertificatePath, MissingKey),
|
||||
},
|
||||
}
|
||||
|
||||
for _, anInvalididTLSConfig := range invalidTLSConfig {
|
||||
tlsConfig, err := NewTLSConfig(anInvalididTLSConfig.configTLSConfig)
|
||||
if tlsConfig != nil && err == nil {
|
||||
t.Errorf("The TLS Config could be created even with this %+v", anInvalididTLSConfig.configTLSConfig)
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(err.Error(), anInvalididTLSConfig.errorMessage) {
|
||||
t.Errorf("The expected error should contain %s, but got %s", anInvalididTLSConfig.errorMessage, err)
|
||||
}
|
||||
}
|
||||
}
|
27
util/httputil/testdata/barney-no-pass.key
vendored
Normal file
27
util/httputil/testdata/barney-no-pass.key
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAxmYjfBZhZbAup9uSULehoqPCv/U+77ETxUNyS2nviWEHDAb/
|
||||
pFS8Btx4oCQ1ECVSyxcUmXSlrvDjMY4sisOHvndNRlGi274M5a8Q5yD1BUqvxq3u
|
||||
XB/+SYNVShBzaswrSjpzMe89AlOPxPjnE14OXh00j2hHunOG4jhlWgJnY0YyvUQQ
|
||||
YWO6KrmKMiZ4MgmY0SWh/ZhlkDJPtkp3aUVM2sheCru/70E9viLGfdlhc2pIMshy
|
||||
wNp4/5IkHBZwbqXFFGX4sRtSXI/auZNvcHOBse+3e3BonWvBWS2lIYbzpX3vLB7B
|
||||
E9BGIxWn1fgNQr14yFPaccSszBvgtmEUONolnwIDAQABAoIBAQC7nBhQHgXKGBl2
|
||||
Z97rb0pstrjRtsLl/Cg68LWi9LEr0tHMIM4bgnkvb8qtfK+k7fZl0BSNrE2EqYvd
|
||||
75jVO2MgzEYJieccLpKZm7u7JGIut9qSYSU2fpaCw6uiVv4dbqY9EhqejKG/km8w
|
||||
j0JMATRK8Qkj1zOE7/wL7dKBlCZaK3u+OT17spuA/21PG/cLiPaSGSA3CU/eqbkU
|
||||
BD6JeBxp33XNTytwWoOvarsigpL0dGqQ7+qhGq6t69qFfWoe9rimV7Ya+tB9zF/U
|
||||
HzOIEspOYvzxe+C7VJjlVFr4haMYmsrO9qRUJ2ofp49OLVdfEANsdVISSvS63BEp
|
||||
gBZN8Ko5AoGBAO1z8y8YCsI+2vBG6nxZ1eMba0KHi3bS8db1TaenJBV22w6WQATh
|
||||
hEaU6VLMFcMvrOUjXN/7HJfnEMyvFT6gb9obPDVEMZw88s9lVN6njgGLZR/jodyN
|
||||
7N7utLopN043Ra0WfEILAXPSz8esT1yn05OZV6AFHxJEWMrX3/4+spCLAoGBANXl
|
||||
RomieVY4u3FF/uzhbzKNNb9ETxrQuexfbangKp5eLniwnr2SQWIbyPzeurwp15J8
|
||||
HvxB2vpNvs1khSwNx9dQfMdiUVPGLWj7MimAHTHsnQ9LVV9W28ghuSWbjQDGTUt1
|
||||
WCCu1MkKIOzupbi+zgsNlI33yilRQKAb9SRxdy29AoGBAOKpvyZiPcrkMxwPpb/k
|
||||
BU7QGpgcSR25CQ+Xg3QZEVHH7h1DgYLnPtwdQ4g8tj1mohTsp7hKvSWndRrdulrY
|
||||
zUyWmOeD3BN2/pTI9rW/nceNp49EPHsLo2O+2xelRlzMWB98ikqEtPM59gt1SSB6
|
||||
N3X6d3GR0fIe+d9PKEtK0Cs3AoGAZ9r8ReXSvm+ra5ON9Nx8znHMEAON2TpRnBi1
|
||||
uY7zgpO+QrGXUfqKrqVJEKbgym4SkribnuYm+fP32eid1McYKk6VV4ZAcMm/0MJv
|
||||
F8Fx64S0ufFdEX6uFl1xdXYyn5apfyMJ2EyrWrYFSKWTZ8GVb753S/tteGRQWa1Z
|
||||
eQly0Y0CgYEAnI6G9KFvXI+MLu5y2LPYAwsesDFzaWwyDl96ioQTA9hNSrjR33Vw
|
||||
xwpiEe0T/WKF8NQ0QWnrQDbTvuCvZUK37TVxscYWuItL6vnBrYqr4Ck0j1BcGwV5
|
||||
jT581A/Vw8JJiR/vfcxgmrFYqoUmkMKDmCN1oImfz09GtQ4jQ1rlxz8=
|
||||
-----END RSA PRIVATE KEY-----
|
96
util/httputil/testdata/barney.crt
vendored
Normal file
96
util/httputil/testdata/barney.crt
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 2 (0x2)
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
Issuer: C=NO, O=Green AS, OU=Green Certificate Authority, CN=Green TLS CA
|
||||
Validity
|
||||
Not Before: Jul 13 04:02:47 2017 GMT
|
||||
Not After : Jul 13 04:02:47 2019 GMT
|
||||
Subject: C=NO, O=Telenor AS, OU=Support, CN=Barney Rubble
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (2048 bit)
|
||||
Modulus:
|
||||
00:c6:66:23:7c:16:61:65:b0:2e:a7:db:92:50:b7:
|
||||
a1:a2:a3:c2:bf:f5:3e:ef:b1:13:c5:43:72:4b:69:
|
||||
ef:89:61:07:0c:06:ff:a4:54:bc:06:dc:78:a0:24:
|
||||
35:10:25:52:cb:17:14:99:74:a5:ae:f0:e3:31:8e:
|
||||
2c:8a:c3:87:be:77:4d:46:51:a2:db:be:0c:e5:af:
|
||||
10:e7:20:f5:05:4a:af:c6:ad:ee:5c:1f:fe:49:83:
|
||||
55:4a:10:73:6a:cc:2b:4a:3a:73:31:ef:3d:02:53:
|
||||
8f:c4:f8:e7:13:5e:0e:5e:1d:34:8f:68:47:ba:73:
|
||||
86:e2:38:65:5a:02:67:63:46:32:bd:44:10:61:63:
|
||||
ba:2a:b9:8a:32:26:78:32:09:98:d1:25:a1:fd:98:
|
||||
65:90:32:4f:b6:4a:77:69:45:4c:da:c8:5e:0a:bb:
|
||||
bf:ef:41:3d:be:22:c6:7d:d9:61:73:6a:48:32:c8:
|
||||
72:c0:da:78:ff:92:24:1c:16:70:6e:a5:c5:14:65:
|
||||
f8:b1:1b:52:5c:8f:da:b9:93:6f:70:73:81:b1:ef:
|
||||
b7:7b:70:68:9d:6b:c1:59:2d:a5:21:86:f3:a5:7d:
|
||||
ef:2c:1e:c1:13:d0:46:23:15:a7:d5:f8:0d:42:bd:
|
||||
78:c8:53:da:71:c4:ac:cc:1b:e0:b6:61:14:38:da:
|
||||
25:9f
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Key Usage: critical
|
||||
Digital Signature
|
||||
X509v3 Basic Constraints:
|
||||
CA:FALSE
|
||||
X509v3 Extended Key Usage:
|
||||
TLS Web Client Authentication
|
||||
X509v3 Subject Key Identifier:
|
||||
F4:17:02:DD:1B:01:AB:C5:BC:17:A4:5C:4B:75:8E:EC:B1:E0:C8:F1
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:AE:42:88:75:DD:05:A6:8E:48:7F:50:69:F9:B7:34:23:49:B8:B4:71
|
||||
|
||||
Authority Information Access:
|
||||
CA Issuers - URI:http://green.no/ca/tls-ca.cer
|
||||
|
||||
X509v3 CRL Distribution Points:
|
||||
|
||||
Full Name:
|
||||
URI:http://green.no/ca/tls-ca.crl
|
||||
|
||||
X509v3 Subject Alternative Name:
|
||||
email:barney@telenor.no
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
96:9a:c5:41:8a:2f:4a:c4:80:d9:2b:1a:cf:07:85:e9:b6:18:
|
||||
01:20:41:b9:c3:d4:ca:d3:2d:66:c3:1d:52:7f:25:d7:92:0c:
|
||||
e9:a9:ae:e6:2e:fa:9d:0a:cf:84:b9:03:f2:63:e3:d3:c9:70:
|
||||
6a:ac:04:5e:a9:2d:a2:43:7a:34:60:f7:a9:32:e1:48:ec:c6:
|
||||
03:ac:b3:06:2e:48:6e:d0:35:11:31:3d:0c:04:66:41:e6:b2:
|
||||
ec:8c:68:f8:e4:bc:47:85:39:60:69:a9:8a:ee:2f:56:88:8a:
|
||||
19:45:d0:84:8e:c2:27:2c:82:9c:07:6c:34:ae:41:61:63:f9:
|
||||
32:cb:8b:33:ea:2c:15:5f:f9:35:b0:3c:51:4d:5f:30:de:0b:
|
||||
88:28:94:79:f3:bd:69:37:ad:12:20:e1:6b:1d:b6:77:d9:83:
|
||||
db:81:a4:53:6c:0f:6a:17:5e:2b:c1:94:c6:42:e3:73:cd:9e:
|
||||
79:1b:8c:89:cd:da:ce:b0:f4:21:c5:32:25:04:6e:68:9f:a7:
|
||||
ca:f4:c5:86:e5:4e:d9:fd:69:73:e6:15:50:6e:76:0f:73:5e:
|
||||
7a:a3:f4:dc:15:4a:ab:bb:3c:9a:fa:9f:01:7a:5c:47:a9:a3:
|
||||
68:1c:49:e0:37:37:77:af:87:07:16:e4:e1:d7:98:39:15:a6:
|
||||
51:5d:4c:db
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAwmgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBdMQswCQYDVQQGEwJOTzER
|
||||
MA8GA1UECgwIR3JlZW4gQVMxJDAiBgNVBAsMG0dyZWVuIENlcnRpZmljYXRlIEF1
|
||||
dGhvcml0eTEVMBMGA1UEAwwMR3JlZW4gVExTIENBMB4XDTE3MDcxMzA0MDI0N1oX
|
||||
DTE5MDcxMzA0MDI0N1owTDELMAkGA1UEBhMCTk8xEzARBgNVBAoMClRlbGVub3Ig
|
||||
QVMxEDAOBgNVBAsMB1N1cHBvcnQxFjAUBgNVBAMMDUJhcm5leSBSdWJibGUwggEi
|
||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGZiN8FmFlsC6n25JQt6Gio8K/
|
||||
9T7vsRPFQ3JLae+JYQcMBv+kVLwG3HigJDUQJVLLFxSZdKWu8OMxjiyKw4e+d01G
|
||||
UaLbvgzlrxDnIPUFSq/Gre5cH/5Jg1VKEHNqzCtKOnMx7z0CU4/E+OcTXg5eHTSP
|
||||
aEe6c4biOGVaAmdjRjK9RBBhY7oquYoyJngyCZjRJaH9mGWQMk+2SndpRUzayF4K
|
||||
u7/vQT2+IsZ92WFzakgyyHLA2nj/kiQcFnBupcUUZfixG1Jcj9q5k29wc4Gx77d7
|
||||
cGida8FZLaUhhvOlfe8sHsET0EYjFafV+A1CvXjIU9pxxKzMG+C2YRQ42iWfAgMB
|
||||
AAGjgfwwgfkwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQCMAAwEwYDVR0lBAwwCgYI
|
||||
KwYBBQUHAwIwHQYDVR0OBBYEFPQXAt0bAavFvBekXEt1juyx4MjxMB8GA1UdIwQY
|
||||
MBaAFK5CiHXdBaaOSH9Qafm3NCNJuLRxMDkGCCsGAQUFBwEBBC0wKzApBggrBgEF
|
||||
BQcwAoYdaHR0cDovL2dyZWVuLm5vL2NhL3Rscy1jYS5jZXIwLgYDVR0fBCcwJTAj
|
||||
oCGgH4YdaHR0cDovL2dyZWVuLm5vL2NhL3Rscy1jYS5jcmwwHAYDVR0RBBUwE4ER
|
||||
YmFybmV5QHRlbGVub3Iubm8wDQYJKoZIhvcNAQEFBQADggEBAJaaxUGKL0rEgNkr
|
||||
Gs8Hhem2GAEgQbnD1MrTLWbDHVJ/JdeSDOmpruYu+p0Kz4S5A/Jj49PJcGqsBF6p
|
||||
LaJDejRg96ky4UjsxgOsswYuSG7QNRExPQwEZkHmsuyMaPjkvEeFOWBpqYruL1aI
|
||||
ihlF0ISOwicsgpwHbDSuQWFj+TLLizPqLBVf+TWwPFFNXzDeC4golHnzvWk3rRIg
|
||||
4WsdtnfZg9uBpFNsD2oXXivBlMZC43PNnnkbjInN2s6w9CHFMiUEbmifp8r0xYbl
|
||||
Ttn9aXPmFVBudg9zXnqj9NwVSqu7PJr6nwF6XEepo2gcSeA3N3evhwcW5OHXmDkV
|
||||
plFdTNs=
|
||||
-----END CERTIFICATE-----
|
1
util/httputil/testdata/bearer.token
vendored
Normal file
1
util/httputil/testdata/bearer.token
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
theanswertothegreatquestionoflifetheuniverseandeverythingisfortytwo
|
96
util/httputil/testdata/server.crt
vendored
Normal file
96
util/httputil/testdata/server.crt
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 4 (0x4)
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
Issuer: C=NO, O=Green AS, OU=Green Certificate Authority, CN=Green TLS CA
|
||||
Validity
|
||||
Not Before: Jul 26 12:47:08 2017 GMT
|
||||
Not After : Jul 26 12:47:08 2019 GMT
|
||||
Subject: C=NO, O=Green AS, OU=Green Certificate Authority, CN=Green TLS CA
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (2048 bit)
|
||||
Modulus:
|
||||
00:97:43:c5:f6:24:b8:ce:30:12:70:ea:17:9c:c0:
|
||||
ce:f2:ef:58:8b:12:7d:46:5e:01:f1:1a:93:b2:3e:
|
||||
d8:cf:99:bc:10:32:f1:12:b0:ef:00:6c:d6:c4:45:
|
||||
85:a8:33:7b:cd:ec:8f:4a:92:d0:5a:4a:41:69:7f:
|
||||
e3:dd:7e:71:d2:21:9c:df:43:b5:6c:60:bb:2a:12:
|
||||
a8:08:cf:c5:ee:08:7d:48:ea:4b:54:e4:82:d9:88:
|
||||
b0:b8:5e:02:12:cb:0e:09:99:b7:5f:42:b6:d7:26:
|
||||
34:0f:4a:e7:fc:ac:9c:59:cd:a1:50:4c:88:5f:f1:
|
||||
d2:7e:5b:21:41:f0:37:50:80:48:71:50:26:61:26:
|
||||
79:64:4b:7e:91:8d:0e:f4:27:fe:19:80:bf:39:55:
|
||||
b7:f3:d0:cd:61:6c:d8:c1:c7:d3:26:77:92:1a:14:
|
||||
42:56:cb:bc:fd:1a:4a:eb:17:d8:8d:af:d1:c0:46:
|
||||
9f:f0:40:5e:0e:34:2f:e7:db:be:66:fd:89:0b:6b:
|
||||
8c:71:c1:0b:0a:c5:c4:c4:eb:7f:44:c1:75:36:23:
|
||||
fd:ed:b6:ee:87:d9:88:47:e1:4b:7c:60:53:e7:85:
|
||||
1c:2f:82:4b:2b:5e:63:1a:49:17:36:2c:fc:39:23:
|
||||
49:22:4d:43:b5:51:22:12:24:9e:31:44:d8:16:4e:
|
||||
a8:eb
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Key Usage: critical
|
||||
Digital Signature, Key Encipherment
|
||||
X509v3 Basic Constraints:
|
||||
CA:FALSE
|
||||
X509v3 Extended Key Usage:
|
||||
TLS Web Server Authentication, TLS Web Client Authentication
|
||||
X509v3 Subject Key Identifier:
|
||||
70:A9:FB:44:66:3C:63:96:E6:05:B2:74:47:C8:18:7E:43:6D:EE:8B
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:AE:42:88:75:DD:05:A6:8E:48:7F:50:69:F9:B7:34:23:49:B8:B4:71
|
||||
|
||||
Authority Information Access:
|
||||
CA Issuers - URI:http://green.no/ca/tls-ca.cer
|
||||
|
||||
X509v3 CRL Distribution Points:
|
||||
|
||||
Full Name:
|
||||
URI:http://green.no/ca/tls-ca.crl
|
||||
|
||||
X509v3 Subject Alternative Name:
|
||||
IP Address:127.0.0.1, IP Address:127.0.0.0, DNS:localhost
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
56:1e:b8:52:ba:f5:72:42:ad:15:71:c1:5e:00:63:c9:4d:56:
|
||||
f2:8d:a3:a9:91:db:d0:b5:1b:88:80:93:80:28:48:b2:d0:a9:
|
||||
d0:ea:de:40:78:cc:57:8c:00:b8:65:99:68:95:98:9b:fb:a2:
|
||||
43:21:ea:00:37:01:77:c7:3b:1a:ec:58:2d:25:9c:ad:23:41:
|
||||
5e:ae:fd:ac:2f:26:81:b8:a7:49:9b:5a:10:fe:ad:c3:86:ab:
|
||||
59:67:b0:c7:81:72:95:60:b5:cb:fc:9f:ad:27:16:50:85:76:
|
||||
33:16:20:2c:1f:c6:14:09:0c:48:9f:c0:19:16:c9:fa:b0:d8:
|
||||
bf:b7:8d:a7:aa:eb:fe:f8:6f:dd:2b:83:ee:c7:8a:df:c8:59:
|
||||
e6:2e:13:1f:57:cc:6f:31:db:f7:b7:5c:3f:78:ad:22:2c:48:
|
||||
bb:6d:c4:ab:dc:c1:76:34:29:d9:1e:67:e0:ac:37:2b:90:f9:
|
||||
71:bd:cf:a1:01:b9:eb:0b:0b:79:2e:8b:52:3d:8e:13:97:c8:
|
||||
05:a3:ef:68:82:49:12:2a:25:1a:48:49:b8:7c:3c:66:0d:74:
|
||||
f9:00:8c:5b:57:d7:76:b1:26:95:86:b2:2e:a3:b2:9c:e0:eb:
|
||||
2d:fc:77:03:8f:cd:56:46:3a:c9:6a:fa:72:e3:19:d8:ef:de:
|
||||
4b:36:95:79
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEQjCCAyqgAwIBAgIBBDANBgkqhkiG9w0BAQUFADBdMQswCQYDVQQGEwJOTzER
|
||||
MA8GA1UECgwIR3JlZW4gQVMxJDAiBgNVBAsMG0dyZWVuIENlcnRpZmljYXRlIEF1
|
||||
dGhvcml0eTEVMBMGA1UEAwwMR3JlZW4gVExTIENBMB4XDTE3MDcyNjEyNDcwOFoX
|
||||
DTE5MDcyNjEyNDcwOFowXTELMAkGA1UEBhMCTk8xETAPBgNVBAoMCEdyZWVuIEFT
|
||||
MSQwIgYDVQQLDBtHcmVlbiBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFTATBgNVBAMM
|
||||
DEdyZWVuIFRMUyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdD
|
||||
xfYkuM4wEnDqF5zAzvLvWIsSfUZeAfEak7I+2M+ZvBAy8RKw7wBs1sRFhagze83s
|
||||
j0qS0FpKQWl/491+cdIhnN9DtWxguyoSqAjPxe4IfUjqS1TkgtmIsLheAhLLDgmZ
|
||||
t19CttcmNA9K5/ysnFnNoVBMiF/x0n5bIUHwN1CASHFQJmEmeWRLfpGNDvQn/hmA
|
||||
vzlVt/PQzWFs2MHH0yZ3khoUQlbLvP0aSusX2I2v0cBGn/BAXg40L+fbvmb9iQtr
|
||||
jHHBCwrFxMTrf0TBdTYj/e227ofZiEfhS3xgU+eFHC+CSyteYxpJFzYs/DkjSSJN
|
||||
Q7VRIhIknjFE2BZOqOsCAwEAAaOCAQswggEHMA4GA1UdDwEB/wQEAwIFoDAJBgNV
|
||||
HRMEAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQU
|
||||
cKn7RGY8Y5bmBbJ0R8gYfkNt7oswHwYDVR0jBBgwFoAUrkKIdd0Fpo5If1Bp+bc0
|
||||
I0m4tHEwOQYIKwYBBQUHAQEELTArMCkGCCsGAQUFBzAChh1odHRwOi8vZ3JlZW4u
|
||||
bm8vY2EvdGxzLWNhLmNlcjAuBgNVHR8EJzAlMCOgIaAfhh1odHRwOi8vZ3JlZW4u
|
||||
bm8vY2EvdGxzLWNhLmNybDAgBgNVHREEGTAXhwR/AAABhwR/AAAAgglsb2NhbGhv
|
||||
c3QwDQYJKoZIhvcNAQEFBQADggEBAFYeuFK69XJCrRVxwV4AY8lNVvKNo6mR29C1
|
||||
G4iAk4AoSLLQqdDq3kB4zFeMALhlmWiVmJv7okMh6gA3AXfHOxrsWC0lnK0jQV6u
|
||||
/awvJoG4p0mbWhD+rcOGq1lnsMeBcpVgtcv8n60nFlCFdjMWICwfxhQJDEifwBkW
|
||||
yfqw2L+3jaeq6/74b90rg+7Hit/IWeYuEx9XzG8x2/e3XD94rSIsSLttxKvcwXY0
|
||||
KdkeZ+CsNyuQ+XG9z6EBuesLC3kui1I9jhOXyAWj72iCSRIqJRpISbh8PGYNdPkA
|
||||
jFtX13axJpWGsi6jspzg6y38dwOPzVZGOslq+nLjGdjv3ks2lXk=
|
||||
-----END CERTIFICATE-----
|
28
util/httputil/testdata/server.key
vendored
Normal file
28
util/httputil/testdata/server.key
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXQ8X2JLjOMBJw
|
||||
6hecwM7y71iLEn1GXgHxGpOyPtjPmbwQMvESsO8AbNbERYWoM3vN7I9KktBaSkFp
|
||||
f+PdfnHSIZzfQ7VsYLsqEqgIz8XuCH1I6ktU5ILZiLC4XgISyw4JmbdfQrbXJjQP
|
||||
Suf8rJxZzaFQTIhf8dJ+WyFB8DdQgEhxUCZhJnlkS36RjQ70J/4ZgL85Vbfz0M1h
|
||||
bNjBx9Mmd5IaFEJWy7z9GkrrF9iNr9HARp/wQF4ONC/n275m/YkLa4xxwQsKxcTE
|
||||
639EwXU2I/3ttu6H2YhH4Ut8YFPnhRwvgksrXmMaSRc2LPw5I0kiTUO1USISJJ4x
|
||||
RNgWTqjrAgMBAAECggEAVurwo4FyV7gzwIIi00XPJLT3ceJL7dUy1HHrEG8gchnq
|
||||
gHxlHdJhYyMnPVydcosyxp75r2YxJtCoSZDdRHbVvGLoGzpy0zW6FnDl8TpCh4aF
|
||||
RxKp+rvbnFf5A9ew5U+cX1PelHRnT7V6EJeAOiaNKOUJnnR7oHX59/UxZQw9HJnX
|
||||
3H4xUdRDmSS3BGKXEswbd7beQjqJtEIkbConfaw32yEod0w2MC0LI4miZ87/6Hsk
|
||||
pyvfpeYxXp4z3BTvFBbf/GEBFuozu63VWHayB9PDmEN/TlphoQpJQihdR2r1lz/H
|
||||
I5QwVlFTDvUSFitNLu+FoaHOfgLprQndbojBXb+tcQKBgQDHCPyM4V7k97RvJgmB
|
||||
ELgZiDYufDrjRLXvFzrrZ7ySU3N+nx3Gz/EhtgbHicDjnRVagHBIwi/QAfBJksCd
|
||||
xcioY5k2OW+8PSTsfFZTAA6XwJp/LGfJik/JjvAVv5CnxBu9lYG4WiSBJFp59ojC
|
||||
zTmfEuB4GPwrjQvzjlqaSpij9QKBgQDCjriwAB2UJIdlgK+DkryLqgim5I4cteB3
|
||||
+juVKz+S8ufFmVvmIXkyDcpyy/26VLC6esy8dV0JoWc4EeitoJvQD1JVZ5+CBTY+
|
||||
r9umx18oe2A/ZgcEf/A3Zd94jM1MwriF6YC+eIOhwhpi7T1xTLf3hc9B0OJ5B1mA
|
||||
vob9rGDtXwKBgD4rkW+UCictNIAvenKFPWxEPuBgT6ij0sx/DhlwCtgOFxprK0rp
|
||||
syFbkVyMq+KtM3lUez5O4c5wfJUOsPnXSOlISxhD8qHy23C/GdvNPcGrGNc2kKjE
|
||||
ek20R0wTzWSJ/jxG0gE6rwJjz5sfJfLrVd9ZbyI0c7hK03vdcHGXcXxtAoGAeGHl
|
||||
BwnbQ3niyTx53VijD2wTVGjhQgSLstEDowYSnTNtk8eTpG6b1gvQc32jLnMOsyQe
|
||||
oJGiEr5q5re2GBDjuDZyxGOMv9/Hs7wOlkCQsbS9Vh0kRHWBRlXjk2zT7yYhFMLp
|
||||
pXFeSW2X9BRFS2CkCCUkm93K9AZHLDE3x6ishNMCgYEAsDsUCzGhI49Aqe+CMP2l
|
||||
WPZl7SEMYS5AtdC5sLtbLYBl8+rMXVGL2opKXqVFYBYkqMJiHGdX3Ub6XSVKLYkN
|
||||
vm4PWmlQS24ZT+jlUl4jk6JU6SAlM/o6ixZl5KNR7yQm6zN2O/RHDeYm0urUQ9tF
|
||||
9dux7LbIFeOoJmoDTWG2+fI=
|
||||
-----END PRIVATE KEY-----
|
172
util/httputil/testdata/tls-ca-chain.pem
vendored
Normal file
172
util/httputil/testdata/tls-ca-chain.pem
vendored
Normal file
|
@ -0,0 +1,172 @@
|
|||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 2 (0x2)
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
Issuer: C=NO, O=Green AS, OU=Green Certificate Authority, CN=Green Root CA
|
||||
Validity
|
||||
Not Before: Jul 13 03:47:20 2017 GMT
|
||||
Not After : Jul 13 03:47:20 2027 GMT
|
||||
Subject: C=NO, O=Green AS, OU=Green Certificate Authority, CN=Green TLS CA
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (2048 bit)
|
||||
Modulus:
|
||||
00:b5:5a:b3:7a:7f:6a:5b:e9:ee:62:ee:4f:61:42:
|
||||
79:93:06:bf:81:fc:9a:1f:b5:80:83:7c:b3:a6:94:
|
||||
54:58:8a:b1:74:cb:c3:b8:3c:23:a8:69:1f:ca:2b:
|
||||
af:be:97:ba:31:73:b5:b8:ce:d9:bf:bf:9a:7a:cf:
|
||||
3a:64:51:83:c9:36:d2:f7:3b:3a:0e:4c:c7:66:2e:
|
||||
bf:1a:df:ce:10:aa:3d:0f:19:74:03:7e:b5:10:bb:
|
||||
e8:37:bd:62:f0:42:2d:df:3d:ca:70:50:10:17:ce:
|
||||
a9:ec:55:8e:87:6f:ce:9a:04:36:14:96:cb:d1:a5:
|
||||
48:d5:d2:87:02:62:93:4e:21:4a:ff:be:44:f1:d2:
|
||||
7e:ed:74:da:c2:51:26:8e:03:a0:c2:bd:bd:5f:b0:
|
||||
50:11:78:fd:ab:1d:04:86:6c:c1:8d:20:bd:05:5f:
|
||||
51:67:c6:d3:07:95:92:2d:92:90:00:c6:9f:2d:dd:
|
||||
36:5c:dc:78:10:7c:f6:68:39:1d:2c:e0:e1:26:64:
|
||||
4f:36:34:66:a7:84:6a:90:15:3a:94:b7:79:b1:47:
|
||||
f5:d2:51:95:54:bf:92:76:9a:b9:88:ee:63:f9:6c:
|
||||
0d:38:c6:b6:1c:06:43:ed:24:1d:bb:6c:72:48:cc:
|
||||
8c:f4:35:bc:43:fe:a6:96:4c:31:5f:82:0d:0d:20:
|
||||
2a:3d
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Key Usage: critical
|
||||
Certificate Sign, CRL Sign
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:TRUE, pathlen:0
|
||||
X509v3 Subject Key Identifier:
|
||||
AE:42:88:75:DD:05:A6:8E:48:7F:50:69:F9:B7:34:23:49:B8:B4:71
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:60:93:53:2F:C7:CF:2A:D7:F3:09:28:F6:3C:AE:9C:50:EC:93:63:E5
|
||||
|
||||
Authority Information Access:
|
||||
CA Issuers - URI:http://green.no/ca/root-ca.cer
|
||||
|
||||
X509v3 CRL Distribution Points:
|
||||
|
||||
Full Name:
|
||||
URI:http://green.no/ca/root-ca.crl
|
||||
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
15:a7:ac:d7:25:9e:2a:d4:d1:14:b4:99:38:3d:2f:73:61:2a:
|
||||
d9:b6:8b:13:ea:fe:db:78:d9:0a:6c:df:26:6e:c1:d5:4a:97:
|
||||
42:19:dd:97:05:03:e4:2b:fc:1e:1f:38:3c:4e:b0:3b:8c:38:
|
||||
ad:2b:65:fa:35:2d:81:8e:e0:f6:0a:89:4c:38:97:01:4b:9c:
|
||||
ac:4e:e1:55:17:ef:0a:ad:a7:eb:1e:4b:86:23:12:f1:52:69:
|
||||
cb:a3:8a:ce:fb:14:8b:86:d7:bb:81:5e:bd:2a:c7:a7:79:58:
|
||||
00:10:c0:db:ff:d4:a5:b9:19:74:b3:23:19:4a:1f:78:4b:a8:
|
||||
b6:f6:20:26:c1:69:f9:89:7f:b8:1c:3b:a2:f9:37:31:80:2c:
|
||||
b0:b6:2b:d2:84:44:d7:42:e4:e6:44:51:04:35:d9:1c:a4:48:
|
||||
c6:b7:35:de:f2:ae:da:4b:ba:c8:09:42:8d:ed:7a:81:dc:ed:
|
||||
9d:f0:de:6e:21:b9:01:1c:ad:64:3d:25:4c:91:94:f1:13:18:
|
||||
bb:89:e9:48:ac:05:73:07:c8:db:bd:69:8e:6f:02:9d:b0:18:
|
||||
c0:b9:e1:a8:b1:17:50:3d:ac:05:6e:6f:63:4f:b1:73:33:60:
|
||||
9a:77:d2:81:8a:01:38:43:e9:4c:3c:90:63:a4:99:4b:d2:1b:
|
||||
f9:1b:ec:ee
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIECzCCAvOgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQGEwJOTzER
|
||||
MA8GA1UECgwIR3JlZW4gQVMxJDAiBgNVBAsMG0dyZWVuIENlcnRpZmljYXRlIEF1
|
||||
dGhvcml0eTEWMBQGA1UEAwwNR3JlZW4gUm9vdCBDQTAeFw0xNzA3MTMwMzQ3MjBa
|
||||
Fw0yNzA3MTMwMzQ3MjBaMF0xCzAJBgNVBAYTAk5PMREwDwYDVQQKDAhHcmVlbiBB
|
||||
UzEkMCIGA1UECwwbR3JlZW4gQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRUwEwYDVQQD
|
||||
DAxHcmVlbiBUTFMgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1
|
||||
WrN6f2pb6e5i7k9hQnmTBr+B/JoftYCDfLOmlFRYirF0y8O4PCOoaR/KK6++l7ox
|
||||
c7W4ztm/v5p6zzpkUYPJNtL3OzoOTMdmLr8a384Qqj0PGXQDfrUQu+g3vWLwQi3f
|
||||
PcpwUBAXzqnsVY6Hb86aBDYUlsvRpUjV0ocCYpNOIUr/vkTx0n7tdNrCUSaOA6DC
|
||||
vb1fsFAReP2rHQSGbMGNIL0FX1FnxtMHlZItkpAAxp8t3TZc3HgQfPZoOR0s4OEm
|
||||
ZE82NGanhGqQFTqUt3mxR/XSUZVUv5J2mrmI7mP5bA04xrYcBkPtJB27bHJIzIz0
|
||||
NbxD/qaWTDFfgg0NICo9AgMBAAGjgdQwgdEwDgYDVR0PAQH/BAQDAgEGMBIGA1Ud
|
||||
EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFK5CiHXdBaaOSH9Qafm3NCNJuLRxMB8G
|
||||
A1UdIwQYMBaAFGCTUy/HzyrX8wko9jyunFDsk2PlMDoGCCsGAQUFBwEBBC4wLDAq
|
||||
BggrBgEFBQcwAoYeaHR0cDovL2dyZWVuLm5vL2NhL3Jvb3QtY2EuY2VyMC8GA1Ud
|
||||
HwQoMCYwJKAioCCGHmh0dHA6Ly9ncmVlbi5uby9jYS9yb290LWNhLmNybDANBgkq
|
||||
hkiG9w0BAQUFAAOCAQEAFaes1yWeKtTRFLSZOD0vc2Eq2baLE+r+23jZCmzfJm7B
|
||||
1UqXQhndlwUD5Cv8Hh84PE6wO4w4rStl+jUtgY7g9gqJTDiXAUucrE7hVRfvCq2n
|
||||
6x5LhiMS8VJpy6OKzvsUi4bXu4FevSrHp3lYABDA2//UpbkZdLMjGUofeEuotvYg
|
||||
JsFp+Yl/uBw7ovk3MYAssLYr0oRE10Lk5kRRBDXZHKRIxrc13vKu2ku6yAlCje16
|
||||
gdztnfDebiG5ARytZD0lTJGU8RMYu4npSKwFcwfI271pjm8CnbAYwLnhqLEXUD2s
|
||||
BW5vY0+xczNgmnfSgYoBOEPpTDyQY6SZS9Ib+Rvs7g==
|
||||
-----END CERTIFICATE-----
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 1 (0x1)
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
Issuer: C=NO, O=Green AS, OU=Green Certificate Authority, CN=Green Root CA
|
||||
Validity
|
||||
Not Before: Jul 13 03:44:39 2017 GMT
|
||||
Not After : Dec 31 23:59:59 2030 GMT
|
||||
Subject: C=NO, O=Green AS, OU=Green Certificate Authority, CN=Green Root CA
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (2048 bit)
|
||||
Modulus:
|
||||
00:a7:e8:ed:de:d4:54:08:41:07:40:d5:c0:43:d6:
|
||||
ab:d3:9e:21:87:c6:13:bf:a7:cf:3d:08:4f:c1:fe:
|
||||
8f:e5:6c:c5:89:97:e5:27:75:26:c3:2a:73:2d:34:
|
||||
7c:6f:35:8d:40:66:61:05:c0:eb:e9:b3:38:47:f8:
|
||||
8b:26:35:2c:df:dc:24:31:fe:72:e3:87:10:d1:f7:
|
||||
a0:57:b7:f3:b1:1a:fe:c7:4b:f8:7b:14:6d:73:08:
|
||||
54:eb:63:3c:0c:ce:22:95:5f:3f:f2:6f:89:ae:63:
|
||||
da:80:74:36:21:13:e8:91:01:58:77:cc:c2:f2:42:
|
||||
bf:eb:b3:60:a7:21:ed:88:24:7f:eb:ff:07:41:9b:
|
||||
93:c8:5f:6a:8e:a6:1a:15:3c:bc:e7:0d:fd:05:fd:
|
||||
3c:c1:1c:1d:1f:57:2b:40:27:62:a1:7c:48:63:c1:
|
||||
45:e7:2f:20:ed:92:1c:42:94:e4:58:70:7a:b6:d2:
|
||||
85:c5:61:d8:cd:c6:37:6b:72:3b:7f:af:55:81:d6:
|
||||
9d:dc:10:c9:d8:0e:81:e4:5e:40:13:2f:20:e8:6b:
|
||||
46:81:ce:88:47:dd:38:71:3d:ef:21:cc:c0:67:cf:
|
||||
0a:f4:e9:3f:a8:9d:26:25:2e:23:1e:a3:11:18:cb:
|
||||
d1:70:1c:9e:7d:09:b1:a4:20:dc:95:15:1d:49:cf:
|
||||
1b:ad
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Key Usage: critical
|
||||
Certificate Sign, CRL Sign
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:TRUE
|
||||
X509v3 Subject Key Identifier:
|
||||
60:93:53:2F:C7:CF:2A:D7:F3:09:28:F6:3C:AE:9C:50:EC:93:63:E5
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:60:93:53:2F:C7:CF:2A:D7:F3:09:28:F6:3C:AE:9C:50:EC:93:63:E5
|
||||
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
a7:77:71:8b:1a:e5:5a:5b:87:54:08:bf:07:3e:cb:99:2f:dc:
|
||||
0e:8d:63:94:95:83:19:c9:92:82:d5:cb:5b:8f:1f:86:55:bc:
|
||||
70:01:1d:33:46:ec:99:de:6b:1f:c3:c2:7a:dd:ef:69:ab:96:
|
||||
58:ec:6c:6f:6c:70:82:71:8a:7f:f0:3b:80:90:d5:64:fa:80:
|
||||
27:b8:7b:50:69:98:4b:37:99:ad:bf:a2:5b:93:22:5e:96:44:
|
||||
3c:5a:cf:0c:f4:62:63:4a:6f:72:a7:f6:89:1d:09:26:3d:8f:
|
||||
a8:86:d4:b4:bc:dd:b3:38:ca:c0:59:16:8c:20:1f:89:35:12:
|
||||
b4:2d:c0:e9:de:93:e0:39:76:32:fc:80:db:da:44:26:fd:01:
|
||||
32:74:97:f8:44:ae:fe:05:b1:34:96:13:34:56:73:b4:93:a5:
|
||||
55:56:d1:01:51:9d:9c:55:e7:38:53:28:12:4e:38:72:0c:8f:
|
||||
bd:91:4c:45:48:3b:e1:0d:03:5f:58:40:c9:d3:a0:ac:b3:89:
|
||||
ce:af:27:8a:0f:ab:ec:72:4d:40:77:30:6b:36:fd:32:46:9f:
|
||||
ee:f9:c4:f5:17:06:0f:4b:d3:88:f5:a4:2f:3d:87:9e:f5:26:
|
||||
74:f0:c9:dc:cb:ad:d9:a7:8a:d3:71:15:00:d3:5d:9f:4c:59:
|
||||
3e:24:63:f5
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDnDCCAoSgAwIBAgIBATANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQGEwJOTzER
|
||||
MA8GA1UECgwIR3JlZW4gQVMxJDAiBgNVBAsMG0dyZWVuIENlcnRpZmljYXRlIEF1
|
||||
dGhvcml0eTEWMBQGA1UEAwwNR3JlZW4gUm9vdCBDQTAgFw0xNzA3MTMwMzQ0Mzla
|
||||
GA8yMDMwMTIzMTIzNTk1OVowXjELMAkGA1UEBhMCTk8xETAPBgNVBAoMCEdyZWVu
|
||||
IEFTMSQwIgYDVQQLDBtHcmVlbiBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFjAUBgNV
|
||||
BAMMDUdyZWVuIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQCn6O3e1FQIQQdA1cBD1qvTniGHxhO/p889CE/B/o/lbMWJl+UndSbDKnMtNHxv
|
||||
NY1AZmEFwOvpszhH+IsmNSzf3CQx/nLjhxDR96BXt/OxGv7HS/h7FG1zCFTrYzwM
|
||||
ziKVXz/yb4muY9qAdDYhE+iRAVh3zMLyQr/rs2CnIe2IJH/r/wdBm5PIX2qOphoV
|
||||
PLznDf0F/TzBHB0fVytAJ2KhfEhjwUXnLyDtkhxClORYcHq20oXFYdjNxjdrcjt/
|
||||
r1WB1p3cEMnYDoHkXkATLyDoa0aBzohH3ThxPe8hzMBnzwr06T+onSYlLiMeoxEY
|
||||
y9FwHJ59CbGkINyVFR1JzxutAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRgk1Mvx88q1/MJKPY8rpxQ7JNj5TAfBgNV
|
||||
HSMEGDAWgBRgk1Mvx88q1/MJKPY8rpxQ7JNj5TANBgkqhkiG9w0BAQUFAAOCAQEA
|
||||
p3dxixrlWluHVAi/Bz7LmS/cDo1jlJWDGcmSgtXLW48fhlW8cAEdM0bsmd5rH8PC
|
||||
et3vaauWWOxsb2xwgnGKf/A7gJDVZPqAJ7h7UGmYSzeZrb+iW5MiXpZEPFrPDPRi
|
||||
Y0pvcqf2iR0JJj2PqIbUtLzdszjKwFkWjCAfiTUStC3A6d6T4Dl2MvyA29pEJv0B
|
||||
MnSX+ESu/gWxNJYTNFZztJOlVVbRAVGdnFXnOFMoEk44cgyPvZFMRUg74Q0DX1hA
|
||||
ydOgrLOJzq8nig+r7HJNQHcwazb9Mkaf7vnE9RcGD0vTiPWkLz2HnvUmdPDJ3Mut
|
||||
2aeK03EVANNdn0xZPiRj9Q==
|
||||
-----END CERTIFICATE-----
|
47
util/testutil/roundtrip.go
Normal file
47
util/testutil/roundtrip.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2017 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 testutil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type roundTrip struct {
|
||||
theResponse *http.Response
|
||||
theError error
|
||||
}
|
||||
|
||||
func (rt *roundTrip) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return rt.theResponse, rt.theError
|
||||
}
|
||||
|
||||
type roundTripCheckRequest struct {
|
||||
checkRequest func(*http.Request)
|
||||
roundTrip
|
||||
}
|
||||
|
||||
func (rt *roundTripCheckRequest) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
rt.checkRequest(r)
|
||||
return rt.theResponse, rt.theError
|
||||
}
|
||||
|
||||
// NewRoundTripCheckRequest creates a new instance of a type that implements http.RoundTripper,
|
||||
// wich before returning theResponse and theError, executes checkRequest against a http.Request.
|
||||
func NewRoundTripCheckRequest(checkRequest func(*http.Request), theResponse *http.Response, theError error) http.RoundTripper {
|
||||
return &roundTripCheckRequest{
|
||||
checkRequest: checkRequest,
|
||||
roundTrip: roundTrip{
|
||||
theResponse: theResponse,
|
||||
theError: theError}}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -4,12 +4,6 @@ body {
|
|||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
th.job_header {
|
||||
font-size: 20px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.state_indicator {
|
||||
padding: 0 4px 0 4px;
|
||||
}
|
||||
|
|
11
web/ui/static/css/targets.css
Normal file
11
web/ui/static/css/targets.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
tr.job_header {
|
||||
font-size: 20px;
|
||||
padding-top: 10px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
tr.job_details > td{
|
||||
padding: 0 !important;
|
||||
}
|
|
@ -927,7 +927,7 @@ function redirectToMigratedURL() {
|
|||
});
|
||||
});
|
||||
var query = $.param(queryObject);
|
||||
window.location = "/graph?" + query;
|
||||
window.location = PATH_PREFIX + "/graph?" + query;
|
||||
}
|
||||
|
||||
$(init);
|
||||
|
|
38
web/ui/static/js/targets.js
Normal file
38
web/ui/static/js/targets.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
function toggle(obj, state){
|
||||
var icon = $(obj).find("i");
|
||||
if (icon.length === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state === true) {
|
||||
icon.removeClass("icon-chevron-down").addClass("icon-chevron-up");
|
||||
} else {
|
||||
icon.removeClass("icon-chevron-up").addClass("icon-chevron-down");
|
||||
}
|
||||
|
||||
$(obj).next().toggle(state);
|
||||
}
|
||||
|
||||
function init() {
|
||||
$(".job_header").click(function() {
|
||||
var job = $(this).find("a").attr("id"),
|
||||
expanderIcon = $(this).find("i.icon-chevron-down");
|
||||
|
||||
if (expanderIcon.length !== 0) {
|
||||
localStorage.setItem(job, false);
|
||||
toggle(this, true);
|
||||
} else {
|
||||
localStorage.setItem(job, true);
|
||||
toggle(this, false);
|
||||
}
|
||||
});
|
||||
|
||||
$(".job_header a").each(function(i, obj) {
|
||||
var selector = $(obj).attr("id");
|
||||
if (localStorage.getItem(selector) === "true") {
|
||||
toggle($(this).parents(".job_header"), false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(init);
|
|
@ -1,58 +1,73 @@
|
|||
{{define "head"}}<!-- nix -->{{end}}
|
||||
{{define "head"}}
|
||||
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/css/targets.css?v={{ buildVersion }}">
|
||||
<script src="{{ pathPrefix }}/static/js/targets.js?v={{ buildVersion }}"></script>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="container-fluid">
|
||||
<h2 id="targets">Targets</h2>
|
||||
<table class="table table-condensed table-bordered table-striped table-hover">
|
||||
<table class="table table-condensed table-bordered table-hover">
|
||||
{{range $job, $pool := .TargetPools }}
|
||||
<thead>
|
||||
<tr><th colspan="5" class="job_header"><a id="job-{{$job}}" href="#job-{{$job}}">{{$job}}</a></th></tr>
|
||||
<tr>
|
||||
<th>Endpoint</th>
|
||||
<th>State</th>
|
||||
<th>Labels</th>
|
||||
<th>Last Scrape</th>
|
||||
<th>Error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $pool}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{.URL | globalURL}}">{{.URL.Scheme}}://{{.URL.Host}}{{.URL.Path}}</a><br>
|
||||
{{range $label, $values := .URL.Query }}
|
||||
{{range $i, $value := $values}}
|
||||
<span class="label label-primary">{{$label}}="{{$value}}"</span>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</td>
|
||||
<td>
|
||||
<span class="alert alert-{{ .Health | healthToClass }} state_indicator text-uppercase">
|
||||
{{.Health}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="cursor-pointer" data-toggle="tooltip" title="" data-html=true data-original-title="<b>Before relabeling:</b>{{range $k, $v := .DiscoveredLabels.Map}}<br>{{$k | html | html}}="{{$v | html | html}}"{{end}}">
|
||||
{{$labels := stripLabels .Labels.Map "job"}}
|
||||
{{range $label, $value := $labels}}
|
||||
<span class="label label-primary">{{$label}}="{{$value}}"</span>
|
||||
{{else}}
|
||||
<span class="label label-default">none</span>
|
||||
{{end}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{{if .LastScrape.IsZero}}Never{{else}}{{since .LastScrape}} ago{{end}}
|
||||
</td>
|
||||
<td>
|
||||
{{if .LastError}}
|
||||
<span class="alert alert-danger state_indicator">{{.LastError}}</span>
|
||||
{{end}}
|
||||
{{$healthy := numHealthy $pool}}
|
||||
{{$total := len $pool}}
|
||||
<tr class="job_header{{if lt $healthy $total}} danger{{end}}">
|
||||
<td colspan="5">
|
||||
<i class="icon-chevron-up"></i><a id="job-{{$job}}" href="#job-{{$job}}">{{$job}} ({{$healthy}}/{{$total}} up)</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class = "job_details">
|
||||
<td>
|
||||
<table class="table table-condensed table-bordered table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Endpoint</th>
|
||||
<th>State</th>
|
||||
<th>Labels</th>
|
||||
<th>Last Scrape</th>
|
||||
<th>Error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $pool}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{.URL | globalURL}}">{{.URL.Scheme}}://{{.URL.Host}}{{.URL.Path}}</a><br>
|
||||
{{range $label, $values := .URL.Query }}
|
||||
{{range $i, $value := $values}}
|
||||
<span class="label label-primary">{{$label}}="{{$value}}"</span>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</td>
|
||||
<td>
|
||||
<span class="alert alert-{{ .Health | healthToClass }} state_indicator text-uppercase">
|
||||
{{.Health}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="cursor-pointer" data-toggle="tooltip" title="" data-html=true data-original-title="<b>Before relabeling:</b>{{range $k, $v := .DiscoveredLabels}}<br>{{$k | html | html}}="{{$v | html | html}}"{{end}}">
|
||||
{{$labels := stripLabels .Labels.Map "job"}}
|
||||
{{range $label, $value := $labels}}
|
||||
<span class="label label-primary">{{$label}}="{{$value}}"</span>
|
||||
{{else}}
|
||||
<span class="label label-default">none</span>
|
||||
{{end}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{{if .LastScrape.IsZero}}Never{{else}}{{since .LastScrape}} ago{{end}}
|
||||
</td>
|
||||
<td>
|
||||
{{if .LastError}}
|
||||
<span class="alert alert-danger state_indicator">{{.LastError}}</span>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
{{end}}
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
93
web/web.go
93
web/web.go
|
@ -28,6 +28,7 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
@ -88,6 +89,8 @@ type Handler struct {
|
|||
externalLabels model.LabelSet
|
||||
mtx sync.RWMutex
|
||||
now func() model.Time
|
||||
|
||||
ready uint32 // ready is uint32 rather than boolean to be able to use atomic functions.
|
||||
}
|
||||
|
||||
// ApplyConfig updates the status state as the new config requires.
|
||||
|
@ -162,7 +165,9 @@ func New(o *Options) *Handler {
|
|||
tsdb: o.Storage,
|
||||
storage: ptsdb.Adapter(o.Storage),
|
||||
notifier: o.Notifier,
|
||||
now: model.Now,
|
||||
|
||||
now: model.Now,
|
||||
ready: 0,
|
||||
}
|
||||
|
||||
h.apiV1 = api_v1.NewAPI(h.queryEngine, h.storage, h.targetManager, h.notifier)
|
||||
|
@ -177,48 +182,49 @@ func New(o *Options) *Handler {
|
|||
|
||||
instrh := prometheus.InstrumentHandler
|
||||
instrf := prometheus.InstrumentHandlerFunc
|
||||
readyf := h.testReady
|
||||
|
||||
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
router.Redirect(w, r, path.Join(o.ExternalURL.Path, "/graph"), http.StatusFound)
|
||||
})
|
||||
|
||||
router.Get("/alerts", instrf("alerts", h.alerts))
|
||||
router.Get("/graph", instrf("graph", h.graph))
|
||||
router.Get("/status", instrf("status", h.status))
|
||||
router.Get("/flags", instrf("flags", h.flags))
|
||||
router.Get("/config", instrf("config", h.config))
|
||||
router.Get("/rules", instrf("rules", h.rules))
|
||||
router.Get("/targets", instrf("targets", h.targets))
|
||||
router.Get("/version", instrf("version", h.version))
|
||||
router.Get("/alerts", readyf(instrf("alerts", h.alerts)))
|
||||
router.Get("/graph", readyf(instrf("graph", h.graph)))
|
||||
router.Get("/status", readyf(instrf("status", h.status)))
|
||||
router.Get("/flags", readyf(instrf("flags", h.flags)))
|
||||
router.Get("/config", readyf(instrf("config", h.config)))
|
||||
router.Get("/rules", readyf(instrf("rules", h.rules)))
|
||||
router.Get("/targets", readyf(instrf("targets", h.targets)))
|
||||
router.Get("/version", readyf(instrf("version", h.version)))
|
||||
|
||||
router.Get("/heap", instrf("heap", dumpHeap))
|
||||
router.Get("/heap", readyf(instrf("heap", dumpHeap)))
|
||||
|
||||
router.Get("/metrics", prometheus.Handler().ServeHTTP)
|
||||
|
||||
router.Get("/federate", instrh("federate", httputil.CompressionHandler{
|
||||
router.Get("/federate", readyf(instrh("federate", httputil.CompressionHandler{
|
||||
Handler: http.HandlerFunc(h.federation),
|
||||
}))
|
||||
})))
|
||||
|
||||
router.Get("/consoles/*filepath", instrf("consoles", h.consoles))
|
||||
router.Get("/consoles/*filepath", readyf(instrf("consoles", h.consoles)))
|
||||
|
||||
router.Get("/static/*filepath", instrf("static", serveStaticAsset))
|
||||
router.Get("/static/*filepath", readyf(instrf("static", serveStaticAsset)))
|
||||
|
||||
if o.UserAssetsPath != "" {
|
||||
router.Get("/user/*filepath", instrf("user", route.FileServe(o.UserAssetsPath)))
|
||||
router.Get("/user/*filepath", readyf(instrf("user", route.FileServe(o.UserAssetsPath))))
|
||||
}
|
||||
|
||||
if o.EnableLifecycle {
|
||||
router.Post("/-/quit", h.quit)
|
||||
router.Post("/-/reload", h.reload)
|
||||
} else {
|
||||
router.Post("/-/quit", func(w http.ResponseWriter, _ *http.Request) {
|
||||
router.Post("/-/quit", readyf(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte("Lifecycle APIs are not enabled"))
|
||||
})
|
||||
router.Post("/-/reload", func(w http.ResponseWriter, _ *http.Request) {
|
||||
}))
|
||||
router.Post("/-/reload", readyf(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte("Lifecycle APIs are not enabled"))
|
||||
})
|
||||
}))
|
||||
}
|
||||
router.Get("/-/quit", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
|
@ -229,8 +235,17 @@ func New(o *Options) *Handler {
|
|||
w.Write([]byte("Only POST requests allowed"))
|
||||
})
|
||||
|
||||
router.Get("/debug/*subpath", http.DefaultServeMux.ServeHTTP)
|
||||
router.Post("/debug/*subpath", http.DefaultServeMux.ServeHTTP)
|
||||
router.Get("/debug/*subpath", readyf(http.DefaultServeMux.ServeHTTP))
|
||||
router.Post("/debug/*subpath", readyf(http.DefaultServeMux.ServeHTTP))
|
||||
|
||||
router.Get("/-/healthy", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "Prometheus is Healthy.\n")
|
||||
})
|
||||
router.Get("/-/ready", readyf(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "Prometheus is Ready.\n")
|
||||
}))
|
||||
|
||||
return h
|
||||
}
|
||||
|
@ -271,6 +286,32 @@ func serveStaticAsset(w http.ResponseWriter, req *http.Request) {
|
|||
http.ServeContent(w, req, info.Name(), info.ModTime(), bytes.NewReader(file))
|
||||
}
|
||||
|
||||
// Ready sets Handler to be ready.
|
||||
func (h *Handler) Ready() {
|
||||
atomic.StoreUint32(&h.ready, 1)
|
||||
}
|
||||
|
||||
// Verifies whether the server is ready or not.
|
||||
func (h *Handler) isReady() bool {
|
||||
ready := atomic.LoadUint32(&h.ready)
|
||||
if ready == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Checks if server is ready, calls f if it is, returns 503 if it is not.
|
||||
func (h *Handler) testReady(f http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if h.isReady() {
|
||||
f(w, r)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
fmt.Fprintf(w, "Service Unavailable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Quit returns the receive-only quit channel.
|
||||
func (h *Handler) Quit() <-chan struct{} {
|
||||
return h.quitCh
|
||||
|
@ -556,6 +597,16 @@ func tmplFuncs(consolesPath string, opts *Options) template_text.FuncMap {
|
|||
}
|
||||
return u
|
||||
},
|
||||
"numHealthy": func(pool []*retrieval.Target) int {
|
||||
alive := len(pool)
|
||||
for _, p := range pool {
|
||||
if p.Health() != retrieval.HealthGood {
|
||||
alive--
|
||||
}
|
||||
}
|
||||
|
||||
return alive
|
||||
},
|
||||
"healthToClass": func(th retrieval.TargetHealth) string {
|
||||
switch th {
|
||||
case retrieval.HealthUnknown:
|
||||
|
|
|
@ -14,8 +14,11 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGlobalURL(t *testing.T) {
|
||||
|
@ -67,3 +70,80 @@ func TestGlobalURL(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadyAndHealthy(t *testing.T) {
|
||||
opts := &Options{
|
||||
ListenAddress: ":9090",
|
||||
ReadTimeout: 30 * time.Second,
|
||||
MaxConnections: 512,
|
||||
Context: nil,
|
||||
Storage: nil,
|
||||
QueryEngine: nil,
|
||||
TargetManager: nil,
|
||||
RuleManager: nil,
|
||||
Notifier: nil,
|
||||
RoutePrefix: "/",
|
||||
MetricsPath: "/metrics/",
|
||||
}
|
||||
|
||||
opts.Flags = map[string]string{}
|
||||
|
||||
webHandler := New(opts)
|
||||
go webHandler.Run(context.Background())
|
||||
|
||||
// Give some time for the web goroutine to run since we need the server
|
||||
// to be up before starting tests.
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
resp, err := http.Get("http://localhost:9090/-/healthy")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected HTTP error %s", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("Path /-/healthy with server unready test, Expected status 200 got: %s", resp.Status)
|
||||
}
|
||||
|
||||
resp, err = http.Get("http://localhost:9090/-/ready")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected HTTP error %s", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusServiceUnavailable {
|
||||
t.Fatalf("Path /-/ready with server unready test, Expected status 503 got: %s", resp.Status)
|
||||
}
|
||||
|
||||
resp, err = http.Get("http://localhost:9090/version")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected HTTP error %s", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusServiceUnavailable {
|
||||
t.Fatalf("Path /version with server unready test, Expected status 503 got: %s", resp.Status)
|
||||
}
|
||||
|
||||
// Set to ready.
|
||||
webHandler.Ready()
|
||||
|
||||
resp, err = http.Get("http://localhost:9090/-/healthy")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected HTTP error %s", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("Path /-/healthy with server ready test, Expected status 200 got: %s", resp.Status)
|
||||
}
|
||||
|
||||
resp, err = http.Get("http://localhost:9090/-/ready")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected HTTP error %s", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("Path /-/ready with server ready test, Expected status 200 got: %s", resp.Status)
|
||||
}
|
||||
|
||||
resp, err = http.Get("http://localhost:9090/version")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected HTTP error %s", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("Path /version with server ready test, Expected status 200 got: %s", resp.Status)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue