mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-24 21:24:05 -08:00
Merge branch 'master' into dev-2.0
This commit is contained in:
commit
87918f3097
15
.github/ISSUE_TEMPLATE.md
vendored
15
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,4 +1,15 @@
|
|||
[NOTICE]: <> (If your question is around usage and not a bug in Prometheus please use: https://groups.google.com/forum/#!forum/prometheus-users)
|
||||
<!--
|
||||
|
||||
Please do *NOT* ask usage questions in Github issues.
|
||||
|
||||
If your issue is not a feature request or bug report use:
|
||||
https://groups.google.com/forum/#!forum/prometheus-users. If
|
||||
you are unsure whether you hit a bug, search and ask in the
|
||||
mailing list first.
|
||||
|
||||
You can find more information at: https://prometheus.io/community/
|
||||
|
||||
-->
|
||||
|
||||
**What did you do?**
|
||||
|
||||
|
@ -12,7 +23,7 @@
|
|||
|
||||
insert output of `uname -srm` here
|
||||
|
||||
* Prometheus version:
|
||||
* Prometheus version:
|
||||
|
||||
insert output of `prometheus -version` here
|
||||
|
||||
|
|
|
@ -1184,6 +1184,7 @@ type OpenstackSDConfig struct {
|
|||
ProjectID string `yaml:"project_id"`
|
||||
DomainName string `yaml:"domain_name"`
|
||||
DomainID string `yaml:"domain_id"`
|
||||
Role OpenStackRole `yaml:"role"`
|
||||
Region string `yaml:"region"`
|
||||
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
|
||||
Port int `yaml:"port"`
|
||||
|
@ -1192,6 +1193,32 @@ type OpenstackSDConfig struct {
|
|||
XXX map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// OpenStackRole is role of the target in OpenStack.
|
||||
type OpenStackRole string
|
||||
|
||||
// The valid options for OpenStackRole.
|
||||
const (
|
||||
// OpenStack document reference
|
||||
// https://docs.openstack.org/nova/pike/admin/arch.html#hypervisors
|
||||
OpenStackRoleHypervisor OpenStackRole = "hypervisor"
|
||||
// OpenStack document reference
|
||||
// https://docs.openstack.org/horizon/pike/user/launch-instances.html
|
||||
OpenStackRoleInstance OpenStackRole = "instance"
|
||||
)
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (c *OpenStackRole) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
if err := unmarshal((*string)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
switch *c {
|
||||
case OpenStackRoleHypervisor, OpenStackRoleInstance:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("Unknown OpenStack SD role %q", *c)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (c *OpenstackSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
*c = DefaultOpenstackSDConfig
|
||||
|
@ -1200,6 +1227,9 @@ func (c *OpenstackSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.Role == "" {
|
||||
return fmt.Errorf("role missing (one of: instance, hypervisor)")
|
||||
}
|
||||
return checkOverflow(c.XXX, "openstack_sd_config")
|
||||
}
|
||||
|
||||
|
|
|
@ -540,15 +540,27 @@ func TestLoadConfig(t *testing.T) {
|
|||
if !reflect.DeepEqual(c, expectedConf) {
|
||||
t.Fatalf("%s: unexpected config result: \n\n%s\n expected\n\n%s", "testdata/conf.good.yml", bgot, bexp)
|
||||
}
|
||||
}
|
||||
|
||||
// String method must not reveal authentication credentials.
|
||||
s := c.String()
|
||||
secretRe := regexp.MustCompile("<secret>")
|
||||
matches := secretRe.FindAllStringIndex(s, -1)
|
||||
if len(matches) != 6 || strings.Contains(s, "mysecret") {
|
||||
t.Fatalf("config's String method reveals authentication credentials.")
|
||||
// YAML marshalling must not reveal authentication credentials.
|
||||
func TestElideSecrets(t *testing.T) {
|
||||
c, err := LoadFile("testdata/conf.good.yml")
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing %s: %s", "testdata/conf.good.yml", err)
|
||||
}
|
||||
|
||||
secretRe := regexp.MustCompile(`\\u003csecret\\u003e|<secret>`)
|
||||
|
||||
config, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
yamlConfig := string(config)
|
||||
|
||||
matches := secretRe.FindAllStringIndex(yamlConfig, -1)
|
||||
if len(matches) != 6 || strings.Contains(yamlConfig, "mysecret") {
|
||||
t.Fatalf("yaml marshal reveals authentication credentials.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigRuleFilesAbsolutePath(t *testing.T) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/util/httputil"
|
||||
"github.com/prometheus/prometheus/util/strutil"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
@ -38,6 +39,8 @@ const (
|
|||
addressLabel = model.MetaLabelPrefix + "consul_address"
|
||||
// nodeLabel is the name for the label containing a target's node name.
|
||||
nodeLabel = model.MetaLabelPrefix + "consul_node"
|
||||
// metaDataLabel is the prefix for the labels mapping to a target's metadata.
|
||||
metaDataLabel = model.MetaLabelPrefix + "consul_metadata_"
|
||||
// tagsLabel is the name of the label containing the tags assigned to the target.
|
||||
tagsLabel = model.MetaLabelPrefix + "consul_tags"
|
||||
// serviceLabel is the name of the label containing the service name.
|
||||
|
@ -294,7 +297,7 @@ func (srv *consulService) watch(ctx context.Context, ch chan<- []*config.TargetG
|
|||
addr = net.JoinHostPort(node.Address, fmt.Sprintf("%d", node.ServicePort))
|
||||
}
|
||||
|
||||
tgroup.Targets = append(tgroup.Targets, model.LabelSet{
|
||||
labels := model.LabelSet{
|
||||
model.AddressLabel: model.LabelValue(addr),
|
||||
addressLabel: model.LabelValue(node.Address),
|
||||
nodeLabel: model.LabelValue(node.Node),
|
||||
|
@ -302,7 +305,15 @@ func (srv *consulService) watch(ctx context.Context, ch chan<- []*config.TargetG
|
|||
serviceAddressLabel: model.LabelValue(node.ServiceAddress),
|
||||
servicePortLabel: model.LabelValue(strconv.Itoa(node.ServicePort)),
|
||||
serviceIDLabel: model.LabelValue(node.ServiceID),
|
||||
})
|
||||
}
|
||||
|
||||
// Add all key/value pairs from the node's metadata as their own labels
|
||||
for k, v := range node.NodeMeta {
|
||||
name := strutil.SanitizeLabelName(k)
|
||||
labels[metaDataLabel+model.LabelName(name)] = model.LabelValue(v)
|
||||
}
|
||||
|
||||
tgroup.Targets = append(tgroup.Targets, labels)
|
||||
}
|
||||
// Check context twice to ensure we always catch cancelation.
|
||||
select {
|
||||
|
|
|
@ -98,7 +98,7 @@ func ProvidersFromConfig(cfg config.ServiceDiscoveryConfig, logger log.Logger) m
|
|||
app("ec2", i, ec2.NewDiscovery(c, logger))
|
||||
}
|
||||
for i, c := range cfg.OpenstackSDConfigs {
|
||||
openstackd, err := openstack.NewDiscovery(c)
|
||||
openstackd, err := openstack.NewDiscovery(c, logger)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot initialize OpenStack discovery: %s", err)
|
||||
continue
|
||||
|
|
|
@ -15,6 +15,7 @@ package file
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
@ -256,6 +257,11 @@ func readFile(filename string) ([]*config.TargetGroup, error) {
|
|||
}
|
||||
|
||||
for i, tg := range targetGroups {
|
||||
if tg == nil {
|
||||
err = errors.New("nil target group item found")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tg.Source = fileSource(filename, i)
|
||||
if tg.Labels == nil {
|
||||
tg.Labels = model.LabelSet{}
|
||||
|
|
|
@ -29,13 +29,17 @@ import (
|
|||
)
|
||||
|
||||
func TestFileSD(t *testing.T) {
|
||||
defer os.Remove("fixtures/_test.yml")
|
||||
defer os.Remove("fixtures/_test.json")
|
||||
testFileSD(t, ".yml")
|
||||
testFileSD(t, ".json")
|
||||
defer os.Remove("fixtures/_test_valid.yml")
|
||||
defer os.Remove("fixtures/_test_valid.json")
|
||||
defer os.Remove("fixtures/_test_invalid_nil.json")
|
||||
defer os.Remove("fixtures/_test_invalid_nil.yml")
|
||||
testFileSD(t, "valid", ".yml", true)
|
||||
testFileSD(t, "valid", ".json", true)
|
||||
testFileSD(t, "invalid_nil", ".json", false)
|
||||
testFileSD(t, "invalid_nil", ".yml", false)
|
||||
}
|
||||
|
||||
func testFileSD(t *testing.T, ext string) {
|
||||
func testFileSD(t *testing.T, prefix, ext string, expect bool) {
|
||||
// As interval refreshing is more of a fallback, we only want to test
|
||||
// whether file watches work as expected.
|
||||
var conf config.FileSDConfig
|
||||
|
@ -56,13 +60,13 @@ func testFileSD(t *testing.T, ext string) {
|
|||
t.Fatalf("Unexpected target groups in file discovery: %s", tgs)
|
||||
}
|
||||
|
||||
newf, err := os.Create("fixtures/_test" + ext)
|
||||
newf, err := os.Create("fixtures/_test_" + prefix + ext)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer newf.Close()
|
||||
|
||||
f, err := os.Open("fixtures/valid" + ext)
|
||||
f, err := os.Open("fixtures/" + prefix + ext)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -80,8 +84,17 @@ retry:
|
|||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatalf("Expected new target group but got none")
|
||||
if expect {
|
||||
t.Fatalf("Expected new target group but got none")
|
||||
} else {
|
||||
// invalid type fsd should always broken down.
|
||||
break retry
|
||||
}
|
||||
case tgs := <-ch:
|
||||
if !expect {
|
||||
t.Fatalf("Unexpected target groups %s, we expected a failure here.", tgs)
|
||||
}
|
||||
|
||||
if len(tgs) != 2 {
|
||||
continue retry // Potentially a partial write, just retry.
|
||||
}
|
||||
|
@ -90,12 +103,12 @@ retry:
|
|||
if _, ok := tg.Labels["foo"]; !ok {
|
||||
t.Fatalf("Label not parsed")
|
||||
}
|
||||
if tg.String() != filepath.FromSlash(fmt.Sprintf("fixtures/_test%s:0", ext)) {
|
||||
if tg.String() != filepath.FromSlash(fmt.Sprintf("fixtures/_test_%s%s:0", prefix, ext)) {
|
||||
t.Fatalf("Unexpected target group %s", tg)
|
||||
}
|
||||
|
||||
tg = tgs[1]
|
||||
if tg.String() != filepath.FromSlash(fmt.Sprintf("fixtures/_test%s:1", ext)) {
|
||||
if tg.String() != filepath.FromSlash(fmt.Sprintf("fixtures/_test_%s%s:1", prefix, ext)) {
|
||||
t.Fatalf("Unexpected target groups %s", tg)
|
||||
}
|
||||
break retry
|
||||
|
@ -135,7 +148,7 @@ retry:
|
|||
}
|
||||
newf.Close()
|
||||
|
||||
os.Rename(newf.Name(), "fixtures/_test"+ext)
|
||||
os.Rename(newf.Name(), "fixtures/_test_"+prefix+ext)
|
||||
|
||||
cancel()
|
||||
<-drained
|
||||
|
|
9
discovery/file/fixtures/invalid_nil.json
Normal file
9
discovery/file/fixtures/invalid_nil.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
{
|
||||
"targets": ["localhost:9090", "example.org:443"],
|
||||
"labels": {
|
||||
"foo": "bar"
|
||||
}
|
||||
},
|
||||
null
|
||||
]
|
5
discovery/file/fixtures/invalid_nil.yml
Normal file
5
discovery/file/fixtures/invalid_nil.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
- targets: ['localhost:9090', 'example.org:443']
|
||||
labels:
|
||||
foo: bar
|
||||
|
||||
- null
|
145
discovery/openstack/hypervisor.go
Normal file
145
discovery/openstack/hypervisor.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
// 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 openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
)
|
||||
|
||||
const (
|
||||
openstackLabelHypervisorHostIP = openstackLabelPrefix + "hypervisor_host_ip"
|
||||
openstackLabelHypervisorHostName = openstackLabelPrefix + "hypervisor_hostname"
|
||||
openstackLabelHypervisorStatus = openstackLabelPrefix + "hypervisor_status"
|
||||
openstackLabelHypervisorState = openstackLabelPrefix + "hypervisor_state"
|
||||
openstackLabelHypervisorType = openstackLabelPrefix + "hypervisor_type"
|
||||
)
|
||||
|
||||
// HypervisorDiscovery discovers OpenStack hypervisors.
|
||||
type HypervisorDiscovery struct {
|
||||
authOpts *gophercloud.AuthOptions
|
||||
region string
|
||||
interval time.Duration
|
||||
logger log.Logger
|
||||
port int
|
||||
}
|
||||
|
||||
// NewHypervisorDiscovery returns a new hypervisor discovery.
|
||||
func NewHypervisorDiscovery(opts *gophercloud.AuthOptions,
|
||||
interval time.Duration, port int, region string, l log.Logger) *HypervisorDiscovery {
|
||||
return &HypervisorDiscovery{authOpts: opts,
|
||||
region: region, interval: interval, port: port, logger: l}
|
||||
}
|
||||
|
||||
// Run implements the TargetProvider interface.
|
||||
func (h *HypervisorDiscovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
||||
// Get an initial set right away.
|
||||
tg, err := h.refresh()
|
||||
if err != nil {
|
||||
h.logger.Error(err)
|
||||
} else {
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(h.interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
tg, err := h.refresh()
|
||||
if err != nil {
|
||||
h.logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HypervisorDiscovery) refresh() (*config.TargetGroup, error) {
|
||||
var err error
|
||||
t0 := time.Now()
|
||||
defer func() {
|
||||
refreshDuration.Observe(time.Since(t0).Seconds())
|
||||
if err != nil {
|
||||
refreshFailuresCount.Inc()
|
||||
}
|
||||
}()
|
||||
|
||||
provider, err := openstack.AuthenticatedClient(*h.authOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack session: %s", err)
|
||||
}
|
||||
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
|
||||
Region: h.region,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack compute session: %s", err)
|
||||
}
|
||||
|
||||
tg := &config.TargetGroup{
|
||||
Source: fmt.Sprintf("OS_" + h.region),
|
||||
}
|
||||
// OpenStack API reference
|
||||
// https://developer.openstack.org/api-ref/compute/#list-hypervisors-details
|
||||
pagerHypervisors := hypervisors.List(client)
|
||||
err = pagerHypervisors.EachPage(func(page pagination.Page) (bool, error) {
|
||||
hypervisorList, err := hypervisors.ExtractHypervisors(page)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not extract hypervisors: %s", err)
|
||||
}
|
||||
for _, hypervisor := range hypervisorList {
|
||||
labels := model.LabelSet{
|
||||
openstackLabelHypervisorHostIP: model.LabelValue(hypervisor.HostIP),
|
||||
}
|
||||
addr := net.JoinHostPort(hypervisor.HostIP, fmt.Sprintf("%d", h.port))
|
||||
labels[model.AddressLabel] = model.LabelValue(addr)
|
||||
labels[openstackLabelHypervisorHostName] = model.LabelValue(hypervisor.HypervisorHostname)
|
||||
labels[openstackLabelHypervisorHostIP] = model.LabelValue(hypervisor.HostIP)
|
||||
labels[openstackLabelHypervisorStatus] = model.LabelValue(hypervisor.Status)
|
||||
labels[openstackLabelHypervisorState] = model.LabelValue(hypervisor.State)
|
||||
labels[openstackLabelHypervisorType] = model.LabelValue(hypervisor.HypervisorType)
|
||||
tg.Targets = append(tg.Targets, labels)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tg, nil
|
||||
}
|
84
discovery/openstack/hypervisor_test.go
Normal file
84
discovery/openstack/hypervisor_test.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
// 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 openstack
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
)
|
||||
|
||||
type OpenstackSDHypervisorTestSuite struct {
|
||||
suite.Suite
|
||||
Mock *SDMock
|
||||
}
|
||||
|
||||
func (s *OpenstackSDHypervisorTestSuite) TearDownSuite() {
|
||||
s.Mock.ShutdownServer()
|
||||
}
|
||||
|
||||
func (s *OpenstackSDHypervisorTestSuite) SetupTest() {
|
||||
s.Mock = NewSDMock(s.T())
|
||||
s.Mock.Setup()
|
||||
|
||||
s.Mock.HandleHypervisorListSuccessfully()
|
||||
|
||||
s.Mock.HandleVersionsSuccessfully()
|
||||
s.Mock.HandleAuthSuccessfully()
|
||||
}
|
||||
|
||||
func TestOpenstackSDHypervisorSuite(t *testing.T) {
|
||||
suite.Run(t, new(OpenstackSDHypervisorTestSuite))
|
||||
}
|
||||
|
||||
func (s *OpenstackSDHypervisorTestSuite) openstackAuthSuccess() (Discovery, error) {
|
||||
conf := config.OpenstackSDConfig{
|
||||
IdentityEndpoint: s.Mock.Endpoint(),
|
||||
Password: "test",
|
||||
Username: "test",
|
||||
DomainName: "12345",
|
||||
Region: "RegionOne",
|
||||
Role: "hypervisor",
|
||||
}
|
||||
return NewDiscovery(&conf, log.Base())
|
||||
}
|
||||
|
||||
func (s *OpenstackSDHypervisorTestSuite) TestOpenstackSDHypervisorRefresh() {
|
||||
hypervisor, _ := s.openstackAuthSuccess()
|
||||
tg, err := hypervisor.refresh()
|
||||
assert.Nil(s.T(), err)
|
||||
require.NotNil(s.T(), tg)
|
||||
require.NotNil(s.T(), tg.Targets)
|
||||
require.Len(s.T(), tg.Targets, 2)
|
||||
|
||||
assert.Equal(s.T(), tg.Targets[0]["__address__"], model.LabelValue("172.16.70.14:0"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_hostname"], model.LabelValue("nc14.cloud.com"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_type"], model.LabelValue("QEMU"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_host_ip"], model.LabelValue("172.16.70.14"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_state"], model.LabelValue("up"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_status"], model.LabelValue("enabled"))
|
||||
|
||||
assert.Equal(s.T(), tg.Targets[1]["__address__"], model.LabelValue("172.16.70.13:0"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_hostname"], model.LabelValue("cc13.cloud.com"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_type"], model.LabelValue("QEMU"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_host_ip"], model.LabelValue("172.16.70.13"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_state"], model.LabelValue("up"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_status"], model.LabelValue("enabled"))
|
||||
}
|
211
discovery/openstack/instance.go
Normal file
211
discovery/openstack/instance.go
Normal file
|
@ -0,0 +1,211 @@
|
|||
// 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 openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/util/strutil"
|
||||
)
|
||||
|
||||
const (
|
||||
openstackLabelPrefix = model.MetaLabelPrefix + "openstack_"
|
||||
openstackLabelInstanceID = openstackLabelPrefix + "instance_id"
|
||||
openstackLabelInstanceName = openstackLabelPrefix + "instance_name"
|
||||
openstackLabelInstanceStatus = openstackLabelPrefix + "instance_status"
|
||||
openstackLabelInstanceFlavor = openstackLabelPrefix + "instance_flavor"
|
||||
openstackLabelPublicIP = openstackLabelPrefix + "public_ip"
|
||||
openstackLabelPrivateIP = openstackLabelPrefix + "private_ip"
|
||||
openstackLabelTagPrefix = openstackLabelPrefix + "tag_"
|
||||
)
|
||||
|
||||
// InstanceDiscovery discovers OpenStack instances.
|
||||
type InstanceDiscovery struct {
|
||||
authOpts *gophercloud.AuthOptions
|
||||
region string
|
||||
interval time.Duration
|
||||
logger log.Logger
|
||||
port int
|
||||
}
|
||||
|
||||
// NewInstanceDiscovery returns a new instance discovery.
|
||||
func NewInstanceDiscovery(opts *gophercloud.AuthOptions,
|
||||
interval time.Duration, port int, region string, l log.Logger) *InstanceDiscovery {
|
||||
return &InstanceDiscovery{authOpts: opts,
|
||||
region: region, interval: interval, port: port, logger: l}
|
||||
}
|
||||
|
||||
// Run implements the TargetProvider interface.
|
||||
func (i *InstanceDiscovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
||||
// Get an initial set right away.
|
||||
tg, err := i.refresh()
|
||||
if err != nil {
|
||||
i.logger.Error(err)
|
||||
} else {
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(i.interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
tg, err := i.refresh()
|
||||
if err != nil {
|
||||
i.logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InstanceDiscovery) refresh() (*config.TargetGroup, error) {
|
||||
var err error
|
||||
t0 := time.Now()
|
||||
defer func() {
|
||||
refreshDuration.Observe(time.Since(t0).Seconds())
|
||||
if err != nil {
|
||||
refreshFailuresCount.Inc()
|
||||
}
|
||||
}()
|
||||
|
||||
provider, err := openstack.AuthenticatedClient(*i.authOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack session: %s", err)
|
||||
}
|
||||
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
|
||||
Region: i.region,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack compute session: %s", err)
|
||||
}
|
||||
|
||||
// OpenStack API reference
|
||||
// https://developer.openstack.org/api-ref/compute/#list-floating-ips
|
||||
pagerFIP := floatingips.List(client)
|
||||
floatingIPList := make(map[string][]string)
|
||||
err = pagerFIP.EachPage(func(page pagination.Page) (bool, error) {
|
||||
result, err := floatingips.ExtractFloatingIPs(page)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not extract floatingips: %s", err)
|
||||
}
|
||||
for _, ip := range result {
|
||||
// Skip not associated ips
|
||||
if ip.InstanceID != "" {
|
||||
floatingIPList[ip.InstanceID] = append(floatingIPList[ip.InstanceID], ip.IP)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// OpenStack API reference
|
||||
// https://developer.openstack.org/api-ref/compute/#list-servers
|
||||
opts := servers.ListOpts{}
|
||||
pager := servers.List(client, opts)
|
||||
tg := &config.TargetGroup{
|
||||
Source: fmt.Sprintf("OS_" + i.region),
|
||||
}
|
||||
err = pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
instanceList, err := servers.ExtractServers(page)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not extract instances: %s", err)
|
||||
}
|
||||
|
||||
for _, s := range instanceList {
|
||||
labels := model.LabelSet{
|
||||
openstackLabelInstanceID: model.LabelValue(s.ID),
|
||||
}
|
||||
if len(s.Addresses) == 0 {
|
||||
i.logger.Info("Got no IP address for instance %s", s.ID)
|
||||
continue
|
||||
}
|
||||
for _, address := range s.Addresses {
|
||||
md, ok := address.([]interface{})
|
||||
if !ok {
|
||||
i.logger.Warn("Invalid type for address, expected array")
|
||||
continue
|
||||
}
|
||||
if len(md) == 0 {
|
||||
i.logger.Debugf("Got no IP address for instance %s", s.ID)
|
||||
continue
|
||||
}
|
||||
md1, ok := md[0].(map[string]interface{})
|
||||
if !ok {
|
||||
i.logger.Warn("Invalid type for address, expected dict")
|
||||
continue
|
||||
}
|
||||
addr, ok := md1["addr"].(string)
|
||||
if !ok {
|
||||
i.logger.Warn("Invalid type for address, expected string")
|
||||
continue
|
||||
}
|
||||
labels[openstackLabelPrivateIP] = model.LabelValue(addr)
|
||||
addr = net.JoinHostPort(addr, fmt.Sprintf("%d", i.port))
|
||||
labels[model.AddressLabel] = model.LabelValue(addr)
|
||||
// Only use first private IP
|
||||
break
|
||||
}
|
||||
if val, ok := floatingIPList[s.ID]; ok && len(val) > 0 {
|
||||
labels[openstackLabelPublicIP] = model.LabelValue(val[0])
|
||||
}
|
||||
labels[openstackLabelInstanceStatus] = model.LabelValue(s.Status)
|
||||
labels[openstackLabelInstanceName] = model.LabelValue(s.Name)
|
||||
id, ok := s.Flavor["id"].(string)
|
||||
if !ok {
|
||||
i.logger.Warn("Invalid type for instance id, excepted string")
|
||||
continue
|
||||
}
|
||||
labels[openstackLabelInstanceFlavor] = model.LabelValue(id)
|
||||
for k, v := range s.Metadata {
|
||||
name := strutil.SanitizeLabelName(k)
|
||||
labels[openstackLabelTagPrefix+model.LabelName(name)] = model.LabelValue(v)
|
||||
}
|
||||
tg.Targets = append(tg.Targets, labels)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tg, nil
|
||||
}
|
|
@ -20,20 +20,21 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
)
|
||||
|
||||
type OpenstackSDTestSuite struct {
|
||||
type OpenstackSDInstanceTestSuite struct {
|
||||
suite.Suite
|
||||
Mock *SDMock
|
||||
}
|
||||
|
||||
func (s *OpenstackSDTestSuite) TearDownSuite() {
|
||||
func (s *OpenstackSDInstanceTestSuite) TearDownSuite() {
|
||||
s.Mock.ShutdownServer()
|
||||
}
|
||||
|
||||
func (s *OpenstackSDTestSuite) SetupTest() {
|
||||
func (s *OpenstackSDInstanceTestSuite) SetupTest() {
|
||||
s.Mock = NewSDMock(s.T())
|
||||
s.Mock.Setup()
|
||||
|
||||
|
@ -44,26 +45,26 @@ func (s *OpenstackSDTestSuite) SetupTest() {
|
|||
s.Mock.HandleAuthSuccessfully()
|
||||
}
|
||||
|
||||
func TestOpenstackSDSuite(t *testing.T) {
|
||||
suite.Run(t, new(OpenstackSDTestSuite))
|
||||
func TestOpenstackSDInstanceSuite(t *testing.T) {
|
||||
suite.Run(t, new(OpenstackSDInstanceTestSuite))
|
||||
}
|
||||
|
||||
func (s *OpenstackSDTestSuite) openstackAuthSuccess() (*Discovery, error) {
|
||||
func (s *OpenstackSDInstanceTestSuite) openstackAuthSuccess() (Discovery, error) {
|
||||
conf := config.OpenstackSDConfig{
|
||||
IdentityEndpoint: s.Mock.Endpoint(),
|
||||
Password: "test",
|
||||
Username: "test",
|
||||
DomainName: "12345",
|
||||
Region: "RegionOne",
|
||||
Role: "instance",
|
||||
}
|
||||
|
||||
return NewDiscovery(&conf)
|
||||
return NewDiscovery(&conf, log.Base())
|
||||
}
|
||||
|
||||
func (s *OpenstackSDTestSuite) TestOpenstackSDRefresh() {
|
||||
d, _ := s.openstackAuthSuccess()
|
||||
func (s *OpenstackSDInstanceTestSuite) TestOpenstackSDInstanceRefresh() {
|
||||
instance, _ := s.openstackAuthSuccess()
|
||||
tg, err := instance.refresh()
|
||||
|
||||
tg, err := d.refresh()
|
||||
assert.Nil(s.T(), err)
|
||||
require.NotNil(s.T(), tg)
|
||||
require.NotNil(s.T(), tg.Targets)
|
||||
|
@ -83,5 +84,4 @@ func (s *OpenstackSDTestSuite) TestOpenstackSDRefresh() {
|
|||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_instance_name"], model.LabelValue("derp"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_instance_status"], model.LabelValue("ACTIVE"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_private_ip"], model.LabelValue("10.0.0.31"))
|
||||
|
||||
}
|
|
@ -182,9 +182,139 @@ func (m *SDMock) HandleAuthSuccessfully() {
|
|||
})
|
||||
}
|
||||
|
||||
const hypervisorListBody = `
|
||||
{
|
||||
"hypervisors": [
|
||||
{
|
||||
"status": "enabled",
|
||||
"service": {
|
||||
"host": "nc14.cloud.com",
|
||||
"disabled_reason": null,
|
||||
"id": 16
|
||||
},
|
||||
"vcpus_used": 18,
|
||||
"hypervisor_type": "QEMU",
|
||||
"local_gb_used": 84,
|
||||
"vcpus": 24,
|
||||
"hypervisor_hostname": "nc14.cloud.com",
|
||||
"memory_mb_used": 24064,
|
||||
"memory_mb": 96484,
|
||||
"current_workload": 1,
|
||||
"state": "up",
|
||||
"host_ip": "172.16.70.14",
|
||||
"cpu_info": "{\"vendor\": \"Intel\", \"model\": \"IvyBridge\", \"arch\": \"x86_64\", \"features\": [\"pge\", \"avx\", \"clflush\", \"sep\", \"syscall\", \"vme\", \"dtes64\", \"msr\", \"fsgsbase\", \"xsave\", \"vmx\", \"erms\", \"xtpr\", \"cmov\", \"smep\", \"ssse3\", \"est\", \"pat\", \"monitor\", \"smx\", \"pbe\", \"lm\", \"tsc\", \"nx\", \"fxsr\", \"tm\", \"sse4.1\", \"pae\", \"sse4.2\", \"pclmuldq\", \"acpi\", \"tsc-deadline\", \"mmx\", \"osxsave\", \"cx8\", \"mce\", \"de\", \"tm2\", \"ht\", \"dca\", \"lahf_lm\", \"popcnt\", \"mca\", \"pdpe1gb\", \"apic\", \"sse\", \"f16c\", \"pse\", \"ds\", \"invtsc\", \"pni\", \"rdtscp\", \"aes\", \"sse2\", \"ss\", \"ds_cpl\", \"pcid\", \"fpu\", \"cx16\", \"pse36\", \"mtrr\", \"pdcm\", \"rdrand\", \"x2apic\"], \"topology\": {\"cores\": 6, \"cells\": 2, \"threads\": 2, \"sockets\": 1}}",
|
||||
"running_vms": 10,
|
||||
"free_disk_gb": 315,
|
||||
"hypervisor_version": 2003000,
|
||||
"disk_available_least": 304,
|
||||
"local_gb": 399,
|
||||
"free_ram_mb": 72420,
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"status": "enabled",
|
||||
"service": {
|
||||
"host": "cc13.cloud.com",
|
||||
"disabled_reason": null,
|
||||
"id": 17
|
||||
},
|
||||
"vcpus_used": 1,
|
||||
"hypervisor_type": "QEMU",
|
||||
"local_gb_used": 20,
|
||||
"vcpus": 24,
|
||||
"hypervisor_hostname": "cc13.cloud.com",
|
||||
"memory_mb_used": 2560,
|
||||
"memory_mb": 96484,
|
||||
"current_workload": 0,
|
||||
"state": "up",
|
||||
"host_ip": "172.16.70.13",
|
||||
"cpu_info": "{\"vendor\": \"Intel\", \"model\": \"IvyBridge\", \"arch\": \"x86_64\", \"features\": [\"pge\", \"avx\", \"clflush\", \"sep\", \"syscall\", \"vme\", \"dtes64\", \"msr\", \"fsgsbase\", \"xsave\", \"vmx\", \"erms\", \"xtpr\", \"cmov\", \"smep\", \"ssse3\", \"est\", \"pat\", \"monitor\", \"smx\", \"pbe\", \"lm\", \"tsc\", \"nx\", \"fxsr\", \"tm\", \"sse4.1\", \"pae\", \"sse4.2\", \"pclmuldq\", \"acpi\", \"tsc-deadline\", \"mmx\", \"osxsave\", \"cx8\", \"mce\", \"de\", \"tm2\", \"ht\", \"dca\", \"lahf_lm\", \"popcnt\", \"mca\", \"pdpe1gb\", \"apic\", \"sse\", \"f16c\", \"pse\", \"ds\", \"invtsc\", \"pni\", \"rdtscp\", \"aes\", \"sse2\", \"ss\", \"ds_cpl\", \"pcid\", \"fpu\", \"cx16\", \"pse36\", \"mtrr\", \"pdcm\", \"rdrand\", \"x2apic\"], \"topology\": {\"cores\": 6, \"cells\": 2, \"threads\": 2, \"sockets\": 1}}",
|
||||
"running_vms": 0,
|
||||
"free_disk_gb": 379,
|
||||
"hypervisor_version": 2003000,
|
||||
"disk_available_least": 384,
|
||||
"local_gb": 399,
|
||||
"free_ram_mb": 93924,
|
||||
"id": 721
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
// HandleHypervisorListSuccessfully mocks os-hypervisors detail call
|
||||
func (m *SDMock) HandleHypervisorListSuccessfully() {
|
||||
m.Mux.HandleFunc("/os-hypervisors/detail", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(m.t, r, "GET")
|
||||
testHeader(m.t, r, "X-Auth-Token", tokenID)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, hypervisorListBody)
|
||||
})
|
||||
}
|
||||
|
||||
const serverListBody = `
|
||||
{
|
||||
"servers": [
|
||||
{
|
||||
"status": "ERROR",
|
||||
"updated": "2014-09-25T13:10:10Z",
|
||||
"hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
|
||||
"OS-EXT-SRV-ATTR:host": "devstack",
|
||||
"addresses": {},
|
||||
"links": [
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/af9bcad9-3c87-477d-9347-b291eabf480e",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/af9bcad9-3c87-477d-9347-b291eabf480e",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"key_name": null,
|
||||
"image": {
|
||||
"id": "f90f6034-2570-4974-8351-6b49732ef2eb",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "error",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "instance-00000010",
|
||||
"OS-SRV-USG:launched_at": "2014-09-25T13:10:10.000000",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
|
||||
"flavor": {
|
||||
"id": "1",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "af9bcad9-3c87-477d-9347-b291eabf480e",
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"user_id": "9349aff8be7545ac9d2f1d00999a23cd",
|
||||
"name": "herp2",
|
||||
"created": "2014-09-25T13:10:02Z",
|
||||
"tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
|
||||
"OS-DCF:diskConfig": "MANUAL",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"accessIPv4": "",
|
||||
"accessIPv6": "",
|
||||
"progress": 0,
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"config_drive": "",
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"status": "ACTIVE",
|
||||
"updated": "2014-09-25T13:10:10Z",
|
||||
|
|
|
@ -14,33 +14,15 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/util/strutil"
|
||||
)
|
||||
|
||||
const (
|
||||
openstackLabelPrefix = model.MetaLabelPrefix + "openstack_"
|
||||
openstackLabelInstanceID = openstackLabelPrefix + "instance_id"
|
||||
openstackLabelInstanceName = openstackLabelPrefix + "instance_name"
|
||||
openstackLabelInstanceStatus = openstackLabelPrefix + "instance_status"
|
||||
openstackLabelInstanceFlavor = openstackLabelPrefix + "instance_flavor"
|
||||
openstackLabelPublicIP = openstackLabelPrefix + "public_ip"
|
||||
openstackLabelPrivateIP = openstackLabelPrefix + "private_ip"
|
||||
openstackLabelTagPrefix = openstackLabelPrefix + "tag_"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -63,15 +45,13 @@ func init() {
|
|||
|
||||
// Discovery periodically performs OpenStack-SD requests. It implements
|
||||
// the TargetProvider interface.
|
||||
type Discovery struct {
|
||||
authOpts *gophercloud.AuthOptions
|
||||
region string
|
||||
interval time.Duration
|
||||
port int
|
||||
type Discovery interface {
|
||||
Run(ctx context.Context, ch chan<- []*config.TargetGroup)
|
||||
refresh() (tg *config.TargetGroup, err error)
|
||||
}
|
||||
|
||||
// NewDiscovery returns a new OpenStackDiscovery which periodically refreshes its targets.
|
||||
func NewDiscovery(conf *config.OpenstackSDConfig) (*Discovery, error) {
|
||||
func NewDiscovery(conf *config.OpenstackSDConfig, l log.Logger) (Discovery, error) {
|
||||
opts := gophercloud.AuthOptions{
|
||||
IdentityEndpoint: conf.IdentityEndpoint,
|
||||
Username: conf.Username,
|
||||
|
@ -82,175 +62,16 @@ func NewDiscovery(conf *config.OpenstackSDConfig) (*Discovery, error) {
|
|||
DomainName: conf.DomainName,
|
||||
DomainID: conf.DomainID,
|
||||
}
|
||||
|
||||
return &Discovery{
|
||||
authOpts: &opts,
|
||||
region: conf.Region,
|
||||
interval: time.Duration(conf.RefreshInterval),
|
||||
port: conf.Port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run implements the TargetProvider interface.
|
||||
func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
||||
// Get an initial set right away.
|
||||
tg, err := d.refresh()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
} else {
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(d.interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
tg, err := d.refresh()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
switch conf.Role {
|
||||
case config.OpenStackRoleHypervisor:
|
||||
hypervisor := NewHypervisorDiscovery(&opts,
|
||||
time.Duration(conf.RefreshInterval), conf.Port, conf.Region, l)
|
||||
return hypervisor, nil
|
||||
case config.OpenStackRoleInstance:
|
||||
instance := NewInstanceDiscovery(&opts,
|
||||
time.Duration(conf.RefreshInterval), conf.Port, conf.Region, l)
|
||||
return instance, nil
|
||||
default:
|
||||
return nil, errors.New("unknown OpenStack discovery role")
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Discovery) refresh() (tg *config.TargetGroup, err error) {
|
||||
t0 := time.Now()
|
||||
defer func() {
|
||||
refreshDuration.Observe(time.Since(t0).Seconds())
|
||||
if err != nil {
|
||||
refreshFailuresCount.Inc()
|
||||
}
|
||||
}()
|
||||
|
||||
provider, err := openstack.AuthenticatedClient(*d.authOpts)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack session: %s", err)
|
||||
}
|
||||
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
|
||||
Region: d.region,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack compute session: %s", err)
|
||||
}
|
||||
|
||||
opts := servers.ListOpts{}
|
||||
pager := servers.List(client, opts)
|
||||
|
||||
tg = &config.TargetGroup{
|
||||
Source: fmt.Sprintf("OS_%s", d.region),
|
||||
}
|
||||
|
||||
pagerFIP := floatingips.List(client)
|
||||
floatingIPList := make(map[string][]string)
|
||||
|
||||
err = pagerFIP.EachPage(func(page pagination.Page) (bool, error) {
|
||||
result, err := floatingips.ExtractFloatingIPs(page)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
for _, ip := range result {
|
||||
// Skip not associated ips
|
||||
if ip.InstanceID != "" {
|
||||
floatingIPList[ip.InstanceID] = append(floatingIPList[ip.InstanceID], ip.IP)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not describe floating IPs: %s", err)
|
||||
}
|
||||
|
||||
err = pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
serverList, err := servers.ExtractServers(page)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not extract servers: %s", err)
|
||||
}
|
||||
|
||||
for _, s := range serverList {
|
||||
labels := model.LabelSet{
|
||||
openstackLabelInstanceID: model.LabelValue(s.ID),
|
||||
}
|
||||
|
||||
for _, address := range s.Addresses {
|
||||
md, ok := address.([]interface{})
|
||||
if !ok {
|
||||
log.Warn("Invalid type for address, expected array")
|
||||
continue
|
||||
}
|
||||
|
||||
if len(md) == 0 {
|
||||
log.Debugf("Got no IP address for instance %s", s.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
md1, ok := md[0].(map[string]interface{})
|
||||
if !ok {
|
||||
log.Warn("Invalid type for address, expected dict")
|
||||
continue
|
||||
}
|
||||
|
||||
addr, ok := md1["addr"].(string)
|
||||
if !ok {
|
||||
log.Warn("Invalid type for address, expected string")
|
||||
continue
|
||||
}
|
||||
|
||||
labels[openstackLabelPrivateIP] = model.LabelValue(addr)
|
||||
|
||||
addr = net.JoinHostPort(addr, fmt.Sprintf("%d", d.port))
|
||||
|
||||
labels[model.AddressLabel] = model.LabelValue(addr)
|
||||
|
||||
// Only use first private IP
|
||||
break
|
||||
}
|
||||
|
||||
if val, ok := floatingIPList[s.ID]; ok {
|
||||
if len(val) > 0 {
|
||||
labels[openstackLabelPublicIP] = model.LabelValue(val[0])
|
||||
}
|
||||
}
|
||||
|
||||
labels[openstackLabelInstanceStatus] = model.LabelValue(s.Status)
|
||||
labels[openstackLabelInstanceName] = model.LabelValue(s.Name)
|
||||
id, ok := s.Flavor["id"].(string)
|
||||
if !ok {
|
||||
log.Warn("Invalid type for instance id, excepted string")
|
||||
continue
|
||||
}
|
||||
labels[openstackLabelInstanceFlavor] = model.LabelValue(id)
|
||||
|
||||
for k, v := range s.Metadata {
|
||||
name := strutil.SanitizeLabelName(k)
|
||||
labels[openstackLabelTagPrefix+model.LabelName(name)] = model.LabelValue(v)
|
||||
}
|
||||
|
||||
tg.Targets = append(tg.Targets, labels)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not describe instances: %s", err)
|
||||
}
|
||||
|
||||
return tg, nil
|
||||
}
|
||||
|
|
|
@ -179,6 +179,7 @@ func buildClients(cfg *config) ([]writer, []reader) {
|
|||
writers = append(writers, c)
|
||||
readers = append(readers, c)
|
||||
}
|
||||
log.Info("Starting up...")
|
||||
return writers, readers
|
||||
}
|
||||
|
||||
|
@ -186,18 +187,21 @@ func serve(addr string, writers []writer, readers []reader) error {
|
|||
http.HandleFunc("/write", func(w http.ResponseWriter, r *http.Request) {
|
||||
compressed, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Errorln("Read error:", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
reqBuf, err := snappy.Decode(nil, compressed)
|
||||
if err != nil {
|
||||
log.Errorln("Decode error:", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req prompb.WriteRequest
|
||||
if err := proto.Unmarshal(reqBuf, &req); err != nil {
|
||||
log.Errorln("Unmarshal error:", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
@ -219,18 +223,21 @@ func serve(addr string, writers []writer, readers []reader) error {
|
|||
http.HandleFunc("/read", func(w http.ResponseWriter, r *http.Request) {
|
||||
compressed, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Errorln("Read error:", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
reqBuf, err := snappy.Decode(nil, compressed)
|
||||
if err != nil {
|
||||
log.Errorln("Decode error:", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req prompb.ReadRequest
|
||||
if err := proto.Unmarshal(reqBuf, &req); err != nil {
|
||||
log.Errorln("Unmarshal error:", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -416,7 +416,7 @@ func TestLabelSetNotReused(t *testing.T) {
|
|||
func makeInputTargetGroup() *config.TargetGroup {
|
||||
return &config.TargetGroup{
|
||||
Targets: []model.LabelSet{
|
||||
model.LabelSet{
|
||||
{
|
||||
model.AddressLabel: model.LabelValue("1.1.1.1:9090"),
|
||||
model.LabelName("notcommon1"): model.LabelValue("label"),
|
||||
},
|
||||
|
|
|
@ -938,7 +938,7 @@ func dateWrapper(ev *evaluator, args Expressions, f func(time.Time) float64) Val
|
|||
v = Vector{
|
||||
Sample{
|
||||
Metric: labels.Labels{},
|
||||
Point: Point{V: float64(ev.Timestamp) / 1000},
|
||||
Point: Point{V: float64(ev.Timestamp) / 1000, T: ev.Timestamp},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -37,7 +37,7 @@ func (rt *roundTripCheckRequest) RoundTrip(r *http.Request) (*http.Response, err
|
|||
}
|
||||
|
||||
// NewRoundTripCheckRequest creates a new instance of a type that implements http.RoundTripper,
|
||||
// wich before returning theResponse and theError, executes checkRequest against a http.Request.
|
||||
// which 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,
|
||||
|
|
3
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/doc.go
generated
vendored
Normal file
3
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
// Package hypervisors gives information and control of the os-hypervisors
|
||||
// portion of the compute API
|
||||
package hypervisors
|
13
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/requests.go
generated
vendored
Normal file
13
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
package hypervisors
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// List makes a request against the API to list hypervisors.
|
||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
||||
return pagination.NewPager(client, hypervisorsListDetailURL(client), func(r pagination.PageResult) pagination.Page {
|
||||
return HypervisorPage{pagination.SinglePageBase(r)}
|
||||
})
|
||||
}
|
161
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/results.go
generated
vendored
Normal file
161
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/results.go
generated
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
package hypervisors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
type Topology struct {
|
||||
Sockets int `json:"sockets"`
|
||||
Cores int `json:"cores"`
|
||||
Threads int `json:"threads"`
|
||||
}
|
||||
|
||||
type CPUInfo struct {
|
||||
Vendor string `json:"vendor"`
|
||||
Arch string `json:"arch"`
|
||||
Model string `json:"model"`
|
||||
Features []string `json:"features"`
|
||||
Topology Topology `json:"topology"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
Host string `json:"host"`
|
||||
ID int `json:"id"`
|
||||
DisabledReason string `json:"disabled_reason"`
|
||||
}
|
||||
|
||||
type Hypervisor struct {
|
||||
// A structure that contains cpu information like arch, model, vendor, features and topology
|
||||
CPUInfo CPUInfo `json:"-"`
|
||||
// The current_workload is the number of tasks the hypervisor is responsible for.
|
||||
// This will be equal or greater than the number of active VMs on the system
|
||||
// (it can be greater when VMs are being deleted and the hypervisor is still cleaning up).
|
||||
CurrentWorkload int `json:"current_workload"`
|
||||
// Status of the hypervisor, either "enabled" or "disabled"
|
||||
Status string `json:"status"`
|
||||
// State of the hypervisor, either "up" or "down"
|
||||
State string `json:"state"`
|
||||
// Actual free disk on this hypervisor in GB
|
||||
DiskAvailableLeast int `json:"disk_available_least"`
|
||||
// The hypervisor's IP address
|
||||
HostIP string `json:"host_ip"`
|
||||
// The free disk remaining on this hypervisor in GB
|
||||
FreeDiskGB int `json:"-"`
|
||||
// The free RAM in this hypervisor in MB
|
||||
FreeRamMB int `json:"free_ram_mb"`
|
||||
// The hypervisor host name
|
||||
HypervisorHostname string `json:"hypervisor_hostname"`
|
||||
// The hypervisor type
|
||||
HypervisorType string `json:"hypervisor_type"`
|
||||
// The hypervisor version
|
||||
HypervisorVersion int `json:"-"`
|
||||
// Unique ID of the hypervisor
|
||||
ID int `json:"id"`
|
||||
// The disk in this hypervisor in GB
|
||||
LocalGB int `json:"-"`
|
||||
// The disk used in this hypervisor in GB
|
||||
LocalGBUsed int `json:"local_gb_used"`
|
||||
// The memory of this hypervisor in MB
|
||||
MemoryMB int `json:"memory_mb"`
|
||||
// The memory used in this hypervisor in MB
|
||||
MemoryMBUsed int `json:"memory_mb_used"`
|
||||
// The number of running vms on this hypervisor
|
||||
RunningVMs int `json:"running_vms"`
|
||||
// The hypervisor service object
|
||||
Service Service `json:"service"`
|
||||
// The number of vcpu in this hypervisor
|
||||
VCPUs int `json:"vcpus"`
|
||||
// The number of vcpu used in this hypervisor
|
||||
VCPUsUsed int `json:"vcpus_used"`
|
||||
}
|
||||
|
||||
func (r *Hypervisor) UnmarshalJSON(b []byte) error {
|
||||
|
||||
type tmp Hypervisor
|
||||
var s struct {
|
||||
tmp
|
||||
CPUInfo interface{} `json:"cpu_info"`
|
||||
HypervisorVersion interface{} `json:"hypervisor_version"`
|
||||
FreeDiskGB interface{} `json:"free_disk_gb"`
|
||||
LocalGB interface{} `json:"local_gb"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = Hypervisor(s.tmp)
|
||||
|
||||
// Newer versions pass the CPU into around as the correct types, this just needs
|
||||
// converting and copying into place. Older versions pass CPU info around as a string
|
||||
// and can simply be unmarshalled by the json parser
|
||||
var tmpb []byte
|
||||
|
||||
switch t := s.CPUInfo.(type) {
|
||||
case string:
|
||||
tmpb = []byte(t)
|
||||
case map[string]interface{}:
|
||||
tmpb, err = json.Marshal(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("CPUInfo has unexpected type: %T", t)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(tmpb, &r.CPUInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// These fields may be passed in in scientific notation
|
||||
switch t := s.HypervisorVersion.(type) {
|
||||
case int:
|
||||
r.HypervisorVersion = t
|
||||
case float64:
|
||||
r.HypervisorVersion = int(t)
|
||||
default:
|
||||
return fmt.Errorf("Hypervisor version of unexpected type")
|
||||
}
|
||||
|
||||
switch t := s.FreeDiskGB.(type) {
|
||||
case int:
|
||||
r.FreeDiskGB = t
|
||||
case float64:
|
||||
r.FreeDiskGB = int(t)
|
||||
default:
|
||||
return fmt.Errorf("Free disk GB of unexpected type")
|
||||
}
|
||||
|
||||
switch t := s.LocalGB.(type) {
|
||||
case int:
|
||||
r.LocalGB = t
|
||||
case float64:
|
||||
r.LocalGB = int(t)
|
||||
default:
|
||||
return fmt.Errorf("Local GB of unexpected type")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type HypervisorPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
func (page HypervisorPage) IsEmpty() (bool, error) {
|
||||
va, err := ExtractHypervisors(page)
|
||||
return len(va) == 0, err
|
||||
}
|
||||
|
||||
func ExtractHypervisors(p pagination.Page) ([]Hypervisor, error) {
|
||||
var h struct {
|
||||
Hypervisors []Hypervisor `json:"hypervisors"`
|
||||
}
|
||||
err := (p.(HypervisorPage)).ExtractInto(&h)
|
||||
return h.Hypervisors, err
|
||||
}
|
7
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/urls.go
generated
vendored
Normal file
7
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package hypervisors
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func hypervisorsListDetailURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("os-hypervisors", "detail")
|
||||
}
|
60
vendor/github.com/hashicorp/consul/api/agent.go
generated
vendored
60
vendor/github.com/hashicorp/consul/api/agent.go
generated
vendored
|
@ -1,6 +1,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
|
@ -73,6 +74,8 @@ type AgentServiceCheck struct {
|
|||
HTTP string `json:",omitempty"`
|
||||
TCP string `json:",omitempty"`
|
||||
Status string `json:",omitempty"`
|
||||
Notes string `json:",omitempty"`
|
||||
TLSSkipVerify bool `json:",omitempty"`
|
||||
|
||||
// In Consul 0.7 and later, checks that are associated with a service
|
||||
// may also contain this optional DeregisterCriticalServiceAfter field,
|
||||
|
@ -114,6 +117,17 @@ func (a *Agent) Self() (map[string]map[string]interface{}, error) {
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// Reload triggers a configuration reload for the agent we are connected to.
|
||||
func (a *Agent) Reload() error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/reload")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodeName is used to get the node name of the agent
|
||||
func (a *Agent) NodeName() (string, error) {
|
||||
if a.nodeName != "" {
|
||||
|
@ -345,6 +359,17 @@ func (a *Agent) Join(addr string, wan bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Leave is used to have the agent gracefully leave the cluster and shutdown
|
||||
func (a *Agent) Leave() error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/leave")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceLeave is used to have the agent eject a failed node
|
||||
func (a *Agent) ForceLeave(node string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node)
|
||||
|
@ -409,3 +434,38 @@ func (a *Agent) DisableNodeMaintenance() error {
|
|||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Monitor returns a channel which will receive streaming logs from the agent
|
||||
// Providing a non-nil stopCh can be used to close the connection and stop the
|
||||
// log stream
|
||||
func (a *Agent) Monitor(loglevel string, stopCh chan struct{}, q *QueryOptions) (chan string, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/monitor")
|
||||
r.setQueryOptions(q)
|
||||
if loglevel != "" {
|
||||
r.params.Add("loglevel", loglevel)
|
||||
}
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logCh := make(chan string, 64)
|
||||
go func() {
|
||||
defer resp.Body.Close()
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
close(logCh)
|
||||
return
|
||||
default:
|
||||
}
|
||||
if scanner.Scan() {
|
||||
logCh <- scanner.Text()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return logCh, nil
|
||||
}
|
||||
|
|
46
vendor/github.com/hashicorp/consul/api/api.go
generated
vendored
46
vendor/github.com/hashicorp/consul/api/api.go
generated
vendored
|
@ -20,6 +20,28 @@ import (
|
|||
"github.com/hashicorp/go-cleanhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
// HTTPAddrEnvName defines an environment variable name which sets
|
||||
// the HTTP address if there is no -http-addr specified.
|
||||
HTTPAddrEnvName = "CONSUL_HTTP_ADDR"
|
||||
|
||||
// HTTPTokenEnvName defines an environment variable name which sets
|
||||
// the HTTP token.
|
||||
HTTPTokenEnvName = "CONSUL_HTTP_TOKEN"
|
||||
|
||||
// HTTPAuthEnvName defines an environment variable name which sets
|
||||
// the HTTP authentication header.
|
||||
HTTPAuthEnvName = "CONSUL_HTTP_AUTH"
|
||||
|
||||
// HTTPSSLEnvName defines an environment variable name which sets
|
||||
// whether or not to use HTTPS.
|
||||
HTTPSSLEnvName = "CONSUL_HTTP_SSL"
|
||||
|
||||
// HTTPSSLVerifyEnvName defines an environment variable name which sets
|
||||
// whether or not to disable certificate checking.
|
||||
HTTPSSLVerifyEnvName = "CONSUL_HTTP_SSL_VERIFY"
|
||||
)
|
||||
|
||||
// QueryOptions are used to parameterize a query
|
||||
type QueryOptions struct {
|
||||
// Providing a datacenter overwrites the DC provided
|
||||
|
@ -52,6 +74,11 @@ type QueryOptions struct {
|
|||
// that node. Setting this to "_agent" will use the agent's node
|
||||
// for the sort.
|
||||
Near string
|
||||
|
||||
// NodeMeta is used to filter results by nodes with the given
|
||||
// metadata key/value pairs. Currently, only one key/value pair can
|
||||
// be provided for filtering.
|
||||
NodeMeta map[string]string
|
||||
}
|
||||
|
||||
// WriteOptions are used to parameterize a write
|
||||
|
@ -181,15 +208,15 @@ func defaultConfig(transportFn func() *http.Transport) *Config {
|
|||
},
|
||||
}
|
||||
|
||||
if addr := os.Getenv("CONSUL_HTTP_ADDR"); addr != "" {
|
||||
if addr := os.Getenv(HTTPAddrEnvName); addr != "" {
|
||||
config.Address = addr
|
||||
}
|
||||
|
||||
if token := os.Getenv("CONSUL_HTTP_TOKEN"); token != "" {
|
||||
if token := os.Getenv(HTTPTokenEnvName); token != "" {
|
||||
config.Token = token
|
||||
}
|
||||
|
||||
if auth := os.Getenv("CONSUL_HTTP_AUTH"); auth != "" {
|
||||
if auth := os.Getenv(HTTPAuthEnvName); auth != "" {
|
||||
var username, password string
|
||||
if strings.Contains(auth, ":") {
|
||||
split := strings.SplitN(auth, ":", 2)
|
||||
|
@ -205,10 +232,10 @@ func defaultConfig(transportFn func() *http.Transport) *Config {
|
|||
}
|
||||
}
|
||||
|
||||
if ssl := os.Getenv("CONSUL_HTTP_SSL"); ssl != "" {
|
||||
if ssl := os.Getenv(HTTPSSLEnvName); ssl != "" {
|
||||
enabled, err := strconv.ParseBool(ssl)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] client: could not parse CONSUL_HTTP_SSL: %s", err)
|
||||
log.Printf("[WARN] client: could not parse %s: %s", HTTPSSLEnvName, err)
|
||||
}
|
||||
|
||||
if enabled {
|
||||
|
@ -216,10 +243,10 @@ func defaultConfig(transportFn func() *http.Transport) *Config {
|
|||
}
|
||||
}
|
||||
|
||||
if verify := os.Getenv("CONSUL_HTTP_SSL_VERIFY"); verify != "" {
|
||||
if verify := os.Getenv(HTTPSSLVerifyEnvName); verify != "" {
|
||||
doVerify, err := strconv.ParseBool(verify)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] client: could not parse CONSUL_HTTP_SSL_VERIFY: %s", err)
|
||||
log.Printf("[WARN] client: could not parse %s: %s", HTTPSSLVerifyEnvName, err)
|
||||
}
|
||||
|
||||
if !doVerify {
|
||||
|
@ -364,6 +391,11 @@ func (r *request) setQueryOptions(q *QueryOptions) {
|
|||
if q.Near != "" {
|
||||
r.params.Set("near", q.Near)
|
||||
}
|
||||
if len(q.NodeMeta) > 0 {
|
||||
for key, value := range q.NodeMeta {
|
||||
r.params.Add("node-meta", key+":"+value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// durToMsec converts a duration to a millisecond specified string. If the
|
||||
|
|
5
vendor/github.com/hashicorp/consul/api/catalog.go
generated
vendored
5
vendor/github.com/hashicorp/consul/api/catalog.go
generated
vendored
|
@ -4,18 +4,22 @@ type Node struct {
|
|||
Node string
|
||||
Address string
|
||||
TaggedAddresses map[string]string
|
||||
Meta map[string]string
|
||||
}
|
||||
|
||||
type CatalogService struct {
|
||||
Node string
|
||||
Address string
|
||||
TaggedAddresses map[string]string
|
||||
NodeMeta map[string]string
|
||||
ServiceID string
|
||||
ServiceName string
|
||||
ServiceAddress string
|
||||
ServiceTags []string
|
||||
ServicePort int
|
||||
ServiceEnableTagOverride bool
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
type CatalogNode struct {
|
||||
|
@ -27,6 +31,7 @@ type CatalogRegistration struct {
|
|||
Node string
|
||||
Address string
|
||||
TaggedAddresses map[string]string
|
||||
NodeMeta map[string]string
|
||||
Datacenter string
|
||||
Service *AgentService
|
||||
Check *AgentCheck
|
||||
|
|
69
vendor/github.com/hashicorp/consul/api/health.go
generated
vendored
69
vendor/github.com/hashicorp/consul/api/health.go
generated
vendored
|
@ -2,6 +2,7 @@ package api
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -11,6 +12,15 @@ const (
|
|||
HealthPassing = "passing"
|
||||
HealthWarning = "warning"
|
||||
HealthCritical = "critical"
|
||||
HealthMaint = "maintenance"
|
||||
)
|
||||
|
||||
const (
|
||||
// NodeMaint is the special key set by a node in maintenance mode.
|
||||
NodeMaint = "_node_maintenance"
|
||||
|
||||
// ServiceMaintPrefix is the prefix for a service in maintenance mode.
|
||||
ServiceMaintPrefix = "_service_maintenance:"
|
||||
)
|
||||
|
||||
// HealthCheck is used to represent a single check
|
||||
|
@ -25,11 +35,56 @@ type HealthCheck struct {
|
|||
ServiceName string
|
||||
}
|
||||
|
||||
// HealthChecks is a collection of HealthCheck structs.
|
||||
type HealthChecks []*HealthCheck
|
||||
|
||||
// AggregatedStatus returns the "best" status for the list of health checks.
|
||||
// Because a given entry may have many service and node-level health checks
|
||||
// attached, this function determines the best representative of the status as
|
||||
// as single string using the following heuristic:
|
||||
//
|
||||
// maintenance > critical > warning > passing
|
||||
//
|
||||
func (c HealthChecks) AggregatedStatus() string {
|
||||
var passing, warning, critical, maintenance bool
|
||||
for _, check := range c {
|
||||
id := string(check.CheckID)
|
||||
if id == NodeMaint || strings.HasPrefix(id, ServiceMaintPrefix) {
|
||||
maintenance = true
|
||||
continue
|
||||
}
|
||||
|
||||
switch check.Status {
|
||||
case HealthPassing:
|
||||
passing = true
|
||||
case HealthWarning:
|
||||
warning = true
|
||||
case HealthCritical:
|
||||
critical = true
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case maintenance:
|
||||
return HealthMaint
|
||||
case critical:
|
||||
return HealthCritical
|
||||
case warning:
|
||||
return HealthWarning
|
||||
case passing:
|
||||
return HealthPassing
|
||||
default:
|
||||
return HealthPassing
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceEntry is used for the health service endpoint
|
||||
type ServiceEntry struct {
|
||||
Node *Node
|
||||
Service *AgentService
|
||||
Checks []*HealthCheck
|
||||
Checks HealthChecks
|
||||
}
|
||||
|
||||
// Health can be used to query the Health endpoints
|
||||
|
@ -43,7 +98,7 @@ func (c *Client) Health() *Health {
|
|||
}
|
||||
|
||||
// Node is used to query for checks belonging to a given node
|
||||
func (h *Health) Node(node string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
func (h *Health) Node(node string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
|
||||
r := h.c.newRequest("GET", "/v1/health/node/"+node)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
|
@ -56,7 +111,7 @@ func (h *Health) Node(node string, q *QueryOptions) ([]*HealthCheck, *QueryMeta,
|
|||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
var out HealthChecks
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -64,7 +119,7 @@ func (h *Health) Node(node string, q *QueryOptions) ([]*HealthCheck, *QueryMeta,
|
|||
}
|
||||
|
||||
// Checks is used to return the checks associated with a service
|
||||
func (h *Health) Checks(service string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
func (h *Health) Checks(service string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
|
||||
r := h.c.newRequest("GET", "/v1/health/checks/"+service)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
|
@ -77,7 +132,7 @@ func (h *Health) Checks(service string, q *QueryOptions) ([]*HealthCheck, *Query
|
|||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
var out HealthChecks
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -115,7 +170,7 @@ func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions)
|
|||
|
||||
// State is used to retrieve all the checks in a given state.
|
||||
// The wildcard "any" state can also be used for all checks.
|
||||
func (h *Health) State(state string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
func (h *Health) State(state string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
|
||||
switch state {
|
||||
case HealthAny:
|
||||
case HealthWarning:
|
||||
|
@ -136,7 +191,7 @@ func (h *Health) State(state string, q *QueryOptions) ([]*HealthCheck, *QueryMet
|
|||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
var out HealthChecks
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
26
vendor/github.com/hashicorp/consul/api/kv.go
generated
vendored
26
vendor/github.com/hashicorp/consul/api/kv.go
generated
vendored
|
@ -50,21 +50,21 @@ type KVOp string
|
|||
|
||||
const (
|
||||
KVSet KVOp = "set"
|
||||
KVDelete = "delete"
|
||||
KVDeleteCAS = "delete-cas"
|
||||
KVDeleteTree = "delete-tree"
|
||||
KVCAS = "cas"
|
||||
KVLock = "lock"
|
||||
KVUnlock = "unlock"
|
||||
KVGet = "get"
|
||||
KVGetTree = "get-tree"
|
||||
KVCheckSession = "check-session"
|
||||
KVCheckIndex = "check-index"
|
||||
KVDelete KVOp = "delete"
|
||||
KVDeleteCAS KVOp = "delete-cas"
|
||||
KVDeleteTree KVOp = "delete-tree"
|
||||
KVCAS KVOp = "cas"
|
||||
KVLock KVOp = "lock"
|
||||
KVUnlock KVOp = "unlock"
|
||||
KVGet KVOp = "get"
|
||||
KVGetTree KVOp = "get-tree"
|
||||
KVCheckSession KVOp = "check-session"
|
||||
KVCheckIndex KVOp = "check-index"
|
||||
)
|
||||
|
||||
// KVTxnOp defines a single operation inside a transaction.
|
||||
type KVTxnOp struct {
|
||||
Verb string
|
||||
Verb KVOp
|
||||
Key string
|
||||
Value []byte
|
||||
Flags uint64
|
||||
|
@ -156,7 +156,7 @@ func (k *KV) Keys(prefix, separator string, q *QueryOptions) ([]string, *QueryMe
|
|||
}
|
||||
|
||||
func (k *KV) getInternal(key string, params map[string]string, q *QueryOptions) (*http.Response, *QueryMeta, error) {
|
||||
r := k.c.newRequest("GET", "/v1/kv/"+key)
|
||||
r := k.c.newRequest("GET", "/v1/kv/"+strings.TrimPrefix(key, "/"))
|
||||
r.setQueryOptions(q)
|
||||
for param, val := range params {
|
||||
r.params.Set(param, val)
|
||||
|
@ -277,7 +277,7 @@ func (k *KV) DeleteTree(prefix string, w *WriteOptions) (*WriteMeta, error) {
|
|||
}
|
||||
|
||||
func (k *KV) deleteInternal(key string, params map[string]string, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
r := k.c.newRequest("DELETE", "/v1/kv/"+key)
|
||||
r := k.c.newRequest("DELETE", "/v1/kv/"+strings.TrimPrefix(key, "/"))
|
||||
r.setWriteOptions(q)
|
||||
for param, val := range params {
|
||||
r.params.Set(param, val)
|
||||
|
|
82
vendor/github.com/hashicorp/consul/api/operator.go
generated
vendored
82
vendor/github.com/hashicorp/consul/api/operator.go
generated
vendored
|
@ -43,6 +43,26 @@ type RaftConfiguration struct {
|
|||
Index uint64
|
||||
}
|
||||
|
||||
// keyringRequest is used for performing Keyring operations
|
||||
type keyringRequest struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
// KeyringResponse is returned when listing the gossip encryption keys
|
||||
type KeyringResponse struct {
|
||||
// Whether this response is for a WAN ring
|
||||
WAN bool
|
||||
|
||||
// The datacenter name this request corresponds to
|
||||
Datacenter string
|
||||
|
||||
// A map of the encryption keys to the number of nodes they're installed on
|
||||
Keys map[string]int
|
||||
|
||||
// The total number of nodes in this ring
|
||||
NumNodes int
|
||||
}
|
||||
|
||||
// RaftGetConfiguration is used to query the current Raft peer set.
|
||||
func (op *Operator) RaftGetConfiguration(q *QueryOptions) (*RaftConfiguration, error) {
|
||||
r := op.c.newRequest("GET", "/v1/operator/raft/configuration")
|
||||
|
@ -79,3 +99,65 @@ func (op *Operator) RaftRemovePeerByAddress(address string, q *WriteOptions) err
|
|||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyringInstall is used to install a new gossip encryption key into the cluster
|
||||
func (op *Operator) KeyringInstall(key string, q *WriteOptions) error {
|
||||
r := op.c.newRequest("POST", "/v1/operator/keyring")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = keyringRequest{
|
||||
Key: key,
|
||||
}
|
||||
_, resp, err := requireOK(op.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyringList is used to list the gossip keys installed in the cluster
|
||||
func (op *Operator) KeyringList(q *QueryOptions) ([]*KeyringResponse, error) {
|
||||
r := op.c.newRequest("GET", "/v1/operator/keyring")
|
||||
r.setQueryOptions(q)
|
||||
_, resp, err := requireOK(op.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out []*KeyringResponse
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// KeyringRemove is used to remove a gossip encryption key from the cluster
|
||||
func (op *Operator) KeyringRemove(key string, q *WriteOptions) error {
|
||||
r := op.c.newRequest("DELETE", "/v1/operator/keyring")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = keyringRequest{
|
||||
Key: key,
|
||||
}
|
||||
_, resp, err := requireOK(op.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyringUse is used to change the active gossip encryption key
|
||||
func (op *Operator) KeyringUse(key string, q *WriteOptions) error {
|
||||
r := op.c.newRequest("PUT", "/v1/operator/keyring")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = keyringRequest{
|
||||
Key: key,
|
||||
}
|
||||
_, resp, err := requireOK(op.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
|
11
vendor/github.com/hashicorp/consul/api/prepared_query.go
generated
vendored
11
vendor/github.com/hashicorp/consul/api/prepared_query.go
generated
vendored
|
@ -167,19 +167,18 @@ func (c *PreparedQuery) Get(queryID string, q *QueryOptions) ([]*PreparedQueryDe
|
|||
}
|
||||
|
||||
// Delete is used to delete a specific prepared query.
|
||||
func (c *PreparedQuery) Delete(queryID string, q *QueryOptions) (*QueryMeta, error) {
|
||||
func (c *PreparedQuery) Delete(queryID string, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := c.c.newRequest("DELETE", "/v1/query/"+queryID)
|
||||
r.setQueryOptions(q)
|
||||
r.setWriteOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
return qm, nil
|
||||
wm := &WriteMeta{}
|
||||
wm.RequestTime = rtt
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Execute is used to execute a specific prepared query. You can execute using
|
||||
|
|
12
vendor/vendor.json
vendored
12
vendor/vendor.json
vendored
|
@ -563,6 +563,12 @@
|
|||
"revision": "caf34a65f60295108141f62929245943bd00f237",
|
||||
"revisionTime": "2017-06-07T03:48:29Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "SKxDWZElN5KwYPPf4QSs9pR0jKg=",
|
||||
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors",
|
||||
"revision": "caf34a65f60295108141f62929245943bd00f237",
|
||||
"revisionTime": "2017-06-07T03:48:29Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "vTyXSR+Znw7/o/70UBOWG0F09r8=",
|
||||
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors",
|
||||
|
@ -645,10 +651,10 @@
|
|||
"revisionTime": "2017-04-04T23:42:07Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "LclVLJYrBi03PBjsVPpgoMbUDQ8=",
|
||||
"checksumSHA1": "/DReHn5j0caPm3thgFD9DmOmibQ=",
|
||||
"path": "github.com/hashicorp/consul/api",
|
||||
"revision": "daacc4be8bee214e3fc4b32a6dd385f5ef1b4c36",
|
||||
"revisionTime": "2016-10-28T04:06:46Z"
|
||||
"revision": "23ce10f8891369f4c7758474c7c808f4e0262701",
|
||||
"revisionTime": "2017-01-12T01:29:24Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Uzyon2091lmwacNsl1hCytjhHtg=",
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/prometheus/common/route"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/pkg/labels"
|
||||
"github.com/prometheus/prometheus/pkg/timestamp"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
|
@ -103,17 +104,19 @@ type API struct {
|
|||
targetRetriever targetRetriever
|
||||
alertmanagerRetriever alertmanagerRetriever
|
||||
|
||||
now func() time.Time
|
||||
now func() time.Time
|
||||
config func() config.Config
|
||||
}
|
||||
|
||||
// NewAPI returns an initialized API type.
|
||||
func NewAPI(qe *promql.Engine, q promql.Queryable, tr targetRetriever, ar alertmanagerRetriever) *API {
|
||||
func NewAPI(qe *promql.Engine, q promql.Queryable, tr targetRetriever, ar alertmanagerRetriever, configFunc func() config.Config) *API {
|
||||
return &API{
|
||||
QueryEngine: qe,
|
||||
Queryable: q,
|
||||
targetRetriever: tr,
|
||||
alertmanagerRetriever: ar,
|
||||
now: time.Now,
|
||||
now: time.Now,
|
||||
config: configFunc,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,6 +150,8 @@ func (api *API) Register(r *route.Router) {
|
|||
|
||||
r.Get("/targets", instr("targets", api.targets))
|
||||
r.Get("/alertmanagers", instr("alertmanagers", api.alertmanagers))
|
||||
|
||||
r.Get("/status/config", instr("config", api.serveConfig))
|
||||
}
|
||||
|
||||
type queryData struct {
|
||||
|
@ -425,6 +430,17 @@ func (api *API) alertmanagers(r *http.Request) (interface{}, *apiError) {
|
|||
return ams, nil
|
||||
}
|
||||
|
||||
type prometheusConfig struct {
|
||||
YAML string `json:"yaml"`
|
||||
}
|
||||
|
||||
func (api *API) serveConfig(r *http.Request) (interface{}, *apiError) {
|
||||
cfg := &prometheusConfig{
|
||||
YAML: api.config().String(),
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func respond(w http.ResponseWriter, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/prometheus/common/route"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/pkg/labels"
|
||||
"github.com/prometheus/prometheus/pkg/timestamp"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
|
@ -47,6 +48,15 @@ func (f alertmanagerRetrieverFunc) Alertmanagers() []*url.URL {
|
|||
return f()
|
||||
}
|
||||
|
||||
var samplePrometheusCfg = config.Config{
|
||||
GlobalConfig: config.GlobalConfig{},
|
||||
AlertingConfig: config.AlertingConfig{},
|
||||
RuleFiles: []string{},
|
||||
ScrapeConfigs: []*config.ScrapeConfig{},
|
||||
RemoteWriteConfigs: []*config.RemoteWriteConfig{},
|
||||
RemoteReadConfigs: []*config.RemoteReadConfig{},
|
||||
}
|
||||
|
||||
func TestEndpoints(t *testing.T) {
|
||||
suite, err := promql.NewTest(t, `
|
||||
load 1m
|
||||
|
@ -92,7 +102,8 @@ func TestEndpoints(t *testing.T) {
|
|||
QueryEngine: suite.QueryEngine(),
|
||||
targetRetriever: tr,
|
||||
alertmanagerRetriever: ar,
|
||||
now: func() time.Time { return now },
|
||||
now: func() time.Time { return now },
|
||||
config: func() config.Config { return samplePrometheusCfg },
|
||||
}
|
||||
|
||||
start := time.Unix(0, 0)
|
||||
|
@ -402,6 +413,19 @@ func TestEndpoints(t *testing.T) {
|
|||
endpoint: api.dropSeries,
|
||||
errType: errorInternal,
|
||||
},
|
||||
{
|
||||
endpoint: api.targets,
|
||||
response: &TargetDiscovery{
|
||||
ActiveTargets: []*Target{
|
||||
{
|
||||
DiscoveredLabels: map[string]string{},
|
||||
Labels: map[string]string{},
|
||||
ScrapeURL: "http://example.com:8080/metrics",
|
||||
Health: "unknown",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
endpoint: api.alertmanagers,
|
||||
response: &AlertmanagerDiscovery{
|
||||
|
@ -412,6 +436,12 @@ func TestEndpoints(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
endpoint: api.serveConfig,
|
||||
response: &prometheusConfig{
|
||||
YAML: samplePrometheusCfg.String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
|
@ -121,7 +121,7 @@ func (h *Handler) federation(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
sort.Sort(byName(vec))
|
||||
|
||||
externalLabels := h.externalLabels.Clone()
|
||||
externalLabels := h.config.GlobalConfig.ExternalLabels.Clone()
|
||||
if _, ok := externalLabels[model.InstanceLabel]; !ok {
|
||||
externalLabels[model.InstanceLabel] = ""
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
)
|
||||
|
||||
|
@ -202,10 +203,13 @@ func TestFederation(t *testing.T) {
|
|||
storage: suite.Storage(),
|
||||
queryEngine: suite.QueryEngine(),
|
||||
now: func() model.Time { return 101 * 60 * 1000 }, // 101min after epoch.
|
||||
config: &config.Config{
|
||||
GlobalConfig: config.GlobalConfig{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, scenario := range scenarios {
|
||||
h.externalLabels = scenario.externalLabels
|
||||
h.config.GlobalConfig.ExternalLabels = scenario.externalLabels
|
||||
req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(
|
||||
"GET http://example.org/federate?" + scenario.params + " HTTP/1.0\r\n\r\n",
|
||||
)))
|
||||
|
|
|
@ -108,7 +108,7 @@ func (fi bindataFileInfo) Sys() interface{} {
|
|||
return nil
|
||||
}
|
||||
|
||||
var _webUiTemplates_baseHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x56\xdd\x6e\xdc\x36\x13\xbd\xcf\x53\xcc\xc7\x04\x5f\xec\x0b\xad\x50\xe4\xa6\x88\x25\x15\x89\xe3\x34\x06\x82\x66\x61\x6f\x83\x16\x45\x61\xcc\x4a\x23\x89\x0e\x45\x2a\xe4\x48\xf5\x62\xb1\xef\x5e\x70\xf5\x53\xad\xec\xb5\xfb\x87\x5e\x89\xa2\x0e\xcf\x0c\xcf\x9c\xa1\x18\xfd\xef\xdd\xa7\xf3\xd5\xcf\xcb\x0b\x28\xb9\x52\xc9\xb3\xc8\x3f\x40\xa1\x2e\x62\x41\x5a\x24\xcf\x00\xa2\x92\x30\xf3\x03\x80\xa8\x22\x46\x28\x99\xeb\x80\xbe\x36\xb2\x8d\xc5\xb9\xd1\x4c\x9a\x83\xd5\xa6\x26\x01\x69\xf7\x16\x0b\xa6\x3b\x0e\x3d\xd5\x19\xa4\x25\x5a\x47\x1c\x37\x9c\x07\xdf\x8a\x9e\x87\x25\x2b\x4a\x96\xd6\x54\xc4\x25\x35\x0e\x56\xb2\x22\xb8\x26\x2b\xc9\xc1\xb9\x51\x8a\x52\x96\x46\x03\xea\x0c\x96\xd6\xa4\xe4\x9c\xd4\x85\x07\xb4\x64\xa3\xb0\x5b\xde\x51\x29\xa9\xbf\x80\x25\x15\x0b\x57\x1a\xcb\x69\xc3\x20\x53\xa3\x05\x94\x96\xf2\x58\x6c\xb7\x50\x23\x97\x4b\x4b\xb9\xbc\x83\xdd\x2e\x74\x8c\x2c\xd3\x50\x56\x45\x98\x63\xeb\xa1\x0b\x99\x9a\xef\xda\x78\xbb\x85\x75\x23\x55\xf6\x99\xac\xf3\xb1\x77\xbb\x21\x5b\x97\x5a\x59\x33\x38\x9b\x1e\xe7\x6b\x49\x67\xc6\x86\xb7\x2e\xbc\xfd\xda\x90\xdd\x2c\x2a\xa9\x17\xb7\xee\x08\x6f\x14\x76\x9c\x7f\x3d\xc0\xda\x18\x76\x6c\xb1\x0e\x5e\x2d\x5e\x2d\xbe\xf1\x01\xc7\xa9\x3f\x1b\x73\x22\x1c\x6f\x6a\xea\xcb\x95\x3a\x27\x7a\x21\x79\xa3\xc8\x95\x44\xfc\x94\x8a\x47\x92\x4a\xdd\x3c\xab\xd4\x1d\x4b\xeb\xdf\x4b\xc6\x47\xad\x47\x4b\x3d\x16\x72\xaa\x7a\x97\x00\x40\x8b\x16\x96\x6f\x56\x1f\x6e\x96\x57\x17\xef\x2f\x7f\x82\x18\xee\x05\x12\x67\x13\xec\xdb\x1f\x2f\x3f\xbe\xbb\xf9\x7c\x71\x75\x7d\xf9\xe9\x87\x1e\x3d\x8f\x34\xe0\x5f\x9c\xe4\x8d\xee\x1c\x7d\x72\x0a\xdb\x7e\xd6\xcf\xbf\xfc\x25\x43\xc6\x80\x4d\x51\x28\xbf\x77\x63\x14\xcb\x5a\xfc\xfa\xf2\x74\xd1\x8f\x4f\x4e\x7b\xf8\xae\x1b\xcc\xca\xb8\xdd\x32\x55\xb5\x42\x26\x10\xbe\x51\x05\x2c\x76\x3b\xdf\xb5\x61\xd7\xb6\x7e\xb8\x36\xd9\xa6\xd7\x59\x63\x0b\xa9\x42\xe7\x62\xa1\xb1\x5d\xa3\x85\xee\x11\x48\xdd\x92\x75\x34\xbc\xe6\xf2\x8e\xb2\x80\x4d\x2d\x06\x7d\xa2\x4c\x8e\x4b\x7d\x9f\xa3\xd4\x64\x83\x5c\x35\x32\x1b\x31\x87\xa8\x9e\xca\xe7\x41\x76\x82\xf1\x19\x35\xcc\x46\xf7\x05\xef\x5e\xc4\x6c\x59\x27\x09\xa4\x46\x29\xac\x1d\x65\x02\x0e\x94\x1a\xe6\x87\x69\xb4\x05\x71\x2c\x9e\x77\xab\x05\xa0\x95\x18\xd0\x5d\x8d\x3a\xa3\x2c\x16\x39\x2a\x8f\xdd\xcf\xfa\xec\xad\x51\x63\xa8\x83\xd4\xbc\x2f\x6a\xd4\x43\x32\xce\x06\x46\xab\x8d\x48\x56\x5d\x3a\x1a\x5b\x59\xa0\xaf\x64\x14\x7a\xdc\x23\x4b\xfd\xd1\x12\xec\xe9\xff\x2b\x68\x14\x76\x52\x1e\xcc\xe1\x4c\xd7\xb5\x45\x9d\x1d\x6d\x25\x31\x39\x94\xa3\x10\x27\x85\x0d\x33\xd9\xce\xea\x2c\xb3\x51\xc2\x59\x90\xa1\x3a\x63\xf9\x0e\xcb\xdf\xa8\x09\x7e\xb0\xdc\x64\xa8\x28\xe7\x59\x55\xb6\xdb\x17\xa9\xd1\xce\x28\x72\xf0\x3a\x86\x61\xbc\x44\x2e\xf7\x7e\x9f\x22\x65\x0e\x23\x78\xf6\x31\x52\x32\x89\x70\xdc\xfd\x04\x26\x92\xf3\x7e\xec\xf7\x1d\x85\x4a\xce\x13\x00\xd2\x19\x3c\xce\x37\x53\x13\x15\x59\x76\x22\x79\xb3\x7f\x3e\xcc\xfb\x38\x43\x61\xb1\x2e\x45\xf2\xbd\x7f\x1c\x5d\x3f\x88\x99\x59\x53\x67\xe6\x37\x3d\x93\x6e\x6f\x82\x8e\xff\xb9\x98\x63\xfb\x86\x9a\x75\xd7\xc8\x04\xd6\xa8\x49\x8b\xee\xfb\xa7\x44\x57\x9b\xba\xa9\x63\xc1\xb6\xa1\x23\xad\x96\x5c\x33\x72\xe3\x0e\xcd\x9b\xa2\x25\x1e\x9d\x7b\xe0\xaf\x7b\xce\x18\x13\xac\x48\x37\xf7\x76\xf4\x94\x6e\x6e\x1f\x5d\x24\x57\x8d\x66\x7f\xb5\xf8\x3f\x56\xf5\x19\xbc\xf5\xe7\x33\x5c\xea\xdc\xd8\xaa\x6f\xe2\x87\x24\x7d\x9a\x3e\x57\x58\x38\xef\x98\xaa\x42\x9d\x05\x1f\xa5\x26\x78\xef\xe7\xfe\x2e\x61\x6a\x74\x2e\x8b\xbd\x07\x73\x59\x34\xf6\x1f\x65\x67\x1b\x45\xfb\xbd\x1f\x35\xf3\xd3\x1c\xdd\x81\xea\x44\xb2\xea\x06\xc7\x78\xa2\xb0\x51\x33\x43\x3e\x68\xf1\x63\x8e\xf4\x97\x49\xf7\x3a\x9c\xfe\xb8\xa5\x11\x30\x9c\xe7\x37\x6b\x85\xfa\x8b\x48\x3e\x90\xaa\xef\xf9\x65\x1e\xe9\x30\x97\x83\x13\x6b\xf2\x12\x85\x1a\xdb\x07\xfe\x9e\xfd\xe5\xf5\x8f\x1f\x68\xf7\xdb\x8c\xc2\xee\x66\xfc\x7b\x00\x00\x00\xff\xff\x64\xff\x3c\x9c\x2a\x0b\x00\x00")
|
||||
var _webUiTemplates_baseHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x56\xdd\x6e\xdc\x36\x13\xbd\xcf\x53\xcc\xc7\x04\x5f\xec\x0b\xad\x50\xe4\xa6\x88\x25\x15\x89\xe3\x34\x06\x82\x66\x61\x6f\x83\x16\x45\x61\xcc\x4a\x23\x89\x0e\x45\x2a\xe4\x48\xf5\x62\xb1\xef\x5e\x70\xf5\x53\xad\xec\xb5\xfb\x87\x5e\x89\xa2\x0e\xcf\x0c\xcf\x9c\xa1\x18\xfd\xef\xdd\xa7\xf3\xd5\xcf\xcb\x0b\x28\xb9\x52\xc9\xb3\xc8\x3f\x40\xa1\x2e\x62\x41\x5a\x24\xcf\x00\xa2\x92\x30\xf3\x03\x80\xa8\x22\x46\x28\x99\xeb\x80\xbe\x36\xb2\x8d\xc5\xb9\xd1\x4c\x9a\x83\xd5\xa6\x26\x01\x69\xf7\x16\x0b\xa6\x3b\x0e\x3d\xd5\x19\xa4\x25\x5a\x47\x1c\x37\x9c\x07\xdf\x8a\x9e\x87\x25\x2b\x4a\x96\xd6\x54\xc4\x25\x35\x0e\x56\xb2\x22\xb8\x26\x2b\xc9\xc1\xb9\x51\x8a\x52\x96\x46\x03\xea\x0c\x96\xd6\xa4\xe4\x9c\xd4\x85\x07\xb4\x64\xa3\xb0\x5b\xde\x51\x29\xa9\xbf\x80\x25\x15\x0b\x57\x1a\xcb\x69\xc3\x20\x53\xa3\x05\x94\x96\xf2\x58\x6c\xb7\x50\x23\x97\x4b\x4b\xb9\xbc\x83\xdd\x2e\x74\x8c\x2c\xd3\x50\x56\x45\x98\x63\xeb\xa1\x0b\x99\x9a\xef\xda\x78\xbb\x85\x75\x23\x55\xf6\x99\xac\xf3\xb1\x77\xbb\x21\x5b\x97\x5a\x59\x33\x38\x9b\x1e\xe7\x6b\x49\x67\xc6\x86\xb7\x2e\xbc\xfd\xda\x90\xdd\x2c\x2a\xa9\x17\xb7\xee\x08\x6f\x14\x76\x9c\x7f\x3d\xc0\xda\x18\x76\x6c\xb1\x0e\x5e\x2d\x5e\x2d\xbe\xf1\x01\xc7\xa9\x3f\x1b\x73\x22\x1c\x6f\x6a\xea\xcb\x95\x3a\x27\x7a\x21\x79\xa3\xc8\x95\x44\xfc\x94\x8a\x47\x92\x4a\xdd\x3c\xab\xd4\x1d\x4b\xeb\xdf\x4b\xc6\x47\xad\x47\x4b\x3d\x16\x72\xaa\x7a\x97\x00\x40\x8b\x16\x96\x6f\x56\x1f\x6e\x96\x57\x17\xef\x2f\x7f\x82\x18\xee\x05\x12\x67\x13\xec\xdb\x1f\x2f\x3f\xbe\xbb\xf9\x7c\x71\x75\x7d\xf9\xe9\x87\x1e\x3d\x8f\x34\xe0\x5f\x9c\xe4\x8d\xee\x1c\x7d\x72\x0a\xdb\x7e\xd6\xcf\xbf\xfc\x25\x43\xc6\x80\x4d\x51\x28\xbf\x77\x63\x14\xcb\x5a\xfc\xfa\xf2\x74\xd1\x8f\x4f\x4e\x7b\xf8\xae\x1b\xcc\xca\xb8\xdd\x32\x55\xb5\x42\x26\x10\xbe\x51\x05\x2c\x76\x3b\xdf\xb5\x61\xd7\xb6\x7e\xb8\x36\xd9\xa6\xd7\x59\x63\x0b\xa9\x42\xe7\x62\xa1\xb1\x5d\xa3\x85\xee\x11\x48\xdd\x92\x75\x34\xbc\xe6\xf2\x8e\xb2\x80\x4d\x2d\x06\x7d\xa2\x4c\x8e\x4b\x7d\x9f\xa3\xd4\x64\x83\x5c\x35\x32\x1b\x31\x87\xa8\x9e\xca\xe7\x41\x76\x82\xf1\x19\x35\xcc\x46\xf7\x05\xef\x5e\xc4\x6c\x59\x27\x09\xa4\x46\x29\xac\x1d\x65\x02\x0e\x94\x1a\xe6\x87\x69\xb4\x05\x71\x2c\x9e\x77\xab\x05\xa0\x95\x18\xd0\x5d\x8d\x3a\xa3\x2c\x16\x39\x2a\x8f\xdd\xcf\xfa\xec\xad\x51\x63\xa8\x83\xd4\xbc\x2f\x6a\xd4\x43\x32\xce\x06\x46\xab\x8d\x48\x56\x5d\x3a\x1a\x5b\x59\xa0\xaf\x64\x14\x7a\xdc\x23\x4b\xfd\xd1\x12\xec\xe9\xff\x2b\x68\x14\x76\x52\x1e\xcc\xe1\x4c\xd7\xb5\x45\x9d\x1d\x6d\x25\x31\x39\x94\xa3\x10\x27\x85\x0d\x33\xd9\xce\xea\x2c\xb3\x51\xc2\x59\x90\xa1\x3a\x63\xf9\x0e\xcb\xdf\xa8\x09\x7e\xb0\xdc\x64\xa8\x28\xe7\x59\x55\xb6\xdb\x17\xa9\xd1\xce\x28\x72\xf0\x3a\x86\x61\xbc\x44\x2e\xf7\x7e\x9f\x22\x65\x0e\x23\x78\xf6\x31\x52\x32\x89\x70\xdc\xfd\x04\x26\x92\xf3\x7e\xec\xf7\x1d\x85\x4a\xce\x13\x00\xd2\x19\x3c\xce\x37\x53\x13\x15\x59\x76\x22\x79\xb3\x7f\x3e\xcc\xfb\x38\x43\x61\xb1\x2e\x45\xf2\xbd\x7f\x1c\x5d\x3f\x88\x99\x59\x53\x67\xe6\x37\x3d\x93\x6e\x6f\x82\x8e\xff\xb9\x98\x63\xfb\x86\x9a\x75\xd7\xc8\x04\xd6\xa8\x49\x8b\xee\xfb\xa7\x44\x57\x9b\xba\xa9\x63\xc1\xb6\xa1\x23\xad\x96\x5c\x33\x72\xe3\x0e\xcd\x9b\xa2\x25\x1e\x9d\x7b\xe0\xaf\x7b\xce\x18\x13\xac\x48\x37\xf7\x76\xf4\x94\x6e\x6e\x1f\x5d\x24\x57\x8d\x66\x7f\xb5\xf8\x3f\x56\xf5\x19\xbc\xf5\xe7\x33\x5c\xea\xdc\xd8\xaa\x6f\xe2\x87\x24\x7d\x9a\x3e\x57\x58\x38\xef\x98\xaa\x42\x9d\x05\x1f\xa5\x26\x78\xef\xe7\xfe\x2e\x61\x6a\x74\x2e\x8b\xbd\x07\x73\x59\x34\xf6\x1f\x65\x67\x1b\x45\xfb\xbd\x1f\x35\xf3\xd3\x1c\xdd\x81\xea\x44\xb2\xea\x06\xc7\x78\xa2\xb0\x51\x33\x43\x3e\x68\xf1\x63\x8e\xf4\x97\x49\xf7\x3a\x9c\xfe\xb8\xa5\x09\x33\x93\x3a\x01\xc3\xa1\x7e\xb3\x56\xa8\xbf\x88\xe4\x03\xa9\xfa\x9e\x69\xe6\xe1\x0e\x13\x3a\x38\xb6\x26\x2f\x51\xa8\xb1\x7d\xe0\x17\xda\xdf\x60\xff\xf8\x8b\x76\xff\xce\x28\xec\xae\xc7\xbf\x07\x00\x00\xff\xff\x54\x18\xdc\x13\x2f\x0b\x00\x00")
|
||||
|
||||
func webUiTemplates_baseHtmlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
|
@ -123,7 +123,7 @@ func webUiTemplates_baseHtml() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "web/ui/templates/_base.html", size: 2858, mode: os.FileMode(420), modTime: time.Unix(1495630073, 0)}
|
||||
info := bindataFileInfo{name: "web/ui/templates/_base.html", size: 2863, mode: os.FileMode(420), modTime: time.Unix(1504523927, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ func webUiTemplatesAlertsHtml() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "web/ui/templates/alerts.html", size: 1836, mode: os.FileMode(420), modTime: time.Unix(1502369604, 0)}
|
||||
info := bindataFileInfo{name: "web/ui/templates/alerts.html", size: 1836, mode: os.FileMode(420), modTime: time.Unix(1504523923, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ func webUiTemplatesRulesHtml() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "web/ui/templates/rules.html", size: 283, mode: os.FileMode(420), modTime: time.Unix(1502369604, 0)}
|
||||
info := bindataFileInfo{name: "web/ui/templates/rules.html", size: 283, mode: os.FileMode(420), modTime: time.Unix(1504523923, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -248,7 +248,7 @@ func webUiTemplatesStatusHtml() (*asset, error) {
|
|||
return a, nil
|
||||
}
|
||||
|
||||
var _webUiTemplatesTargetsHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x56\x4d\x6f\xe3\x36\x13\xbe\xfb\x57\x0c\xf4\x06\x2f\x5a\x60\x65\x01\x0b\xf4\x92\x52\x2a\xd0\x76\x81\x2d\x90\x16\xe9\x66\xb7\x87\x5e\x16\x94\x38\xb6\x98\x30\xa4\x4a\x8e\x8c\x35\xb8\xfc\xef\x05\x29\xc9\x76\x12\xc9\x69\xd3\x2e\x7a\x91\x4d\xce\xcc\x43\x3e\xc3\xf9\xf2\x5e\xe0\x46\x6a\x84\xac\x45\x2e\xb2\x10\x56\x4c\x49\x7d\x07\xb4\xef\xb0\xcc\x08\x3f\x51\xd1\x38\x97\x81\x45\x55\x66\x8e\xf6\x0a\x5d\x8b\x48\x19\xb4\x16\x37\x65\xe6\x3d\x74\x9c\xda\x6b\x8b\x1b\xf9\x09\x42\x28\x1c\x71\x92\x4d\xb4\x29\x88\xdb\x2d\x92\x5b\x37\xce\x7d\xb7\x2b\xbd\x87\xba\x97\x4a\xfc\x86\xd6\x49\xa3\x21\x84\xac\x5a\x31\xd7\x58\xd9\x11\x38\xdb\x2c\x63\xdd\x1e\xa1\x6e\x97\x90\x58\x31\x20\x55\x2b\xef\x51\x8b\x10\x56\xab\x23\xb3\xc6\x68\x42\x4d\x91\x1c\x00\x13\x72\x07\x8d\xe2\xce\x95\x49\xc0\xa5\x46\x9b\x6f\x54\x2f\x45\x56\xad\x00\x00\x58\xfb\x1a\xa4\x28\xb3\xf1\xd0\xac\x7a\x3f\xfc\x61\x45\xfb\x7a\xd0\x00\x60\xc4\x6b\x85\x13\xce\xb0\x48\xdf\xbc\x31\x5a\xa0\x76\x28\xc6\x75\x6d\xac\x40\x7b\x58\xb6\x66\x87\x36\x9b\x60\x00\xbc\xb7\x5c\x6f\x11\x2e\x6e\x4d\xfd\x0a\x2e\x3a\x63\x14\x5c\x96\xb0\x1e\xce\xbc\x36\x46\x39\x48\xf7\x3e\x1a\x5c\xb4\xc8\x15\xb5\xfb\xa8\xa7\xfb\xfb\xb7\xe3\x2a\xd9\x3e\x56\x25\x43\x3c\x01\x2a\xd4\x33\x1a\x91\x88\x9d\x58\xdc\x9a\xfa\x63\x0c\x02\xb4\xde\xcb\x0d\x28\x82\xc3\x49\x03\x4e\x08\x20\xe2\x65\xed\xe8\xe3\x13\x1a\x13\x98\x80\xc6\x28\xd7\x71\x5d\x66\xdf\x3c\x11\x03\x30\x39\x1d\x26\x1b\xa3\xf3\xa6\xc5\x9d\x35\x3a\xef\xbb\xf8\x82\xb2\x62\x3c\x39\xfe\xd6\xd4\xb9\xf7\xd1\x23\x21\x4c\x81\xf6\xbf\x07\x9b\xd5\xf4\x0f\xbe\x3a\xfa\x23\x84\x62\x62\x1c\x02\xf4\xdd\xd7\xac\xe0\x4f\x6e\x58\x90\x78\xb8\xc7\x0a\xb2\xd5\xbc\x4b\xa0\x84\xe4\x14\x81\xc4\xa5\x72\x73\x74\x67\x28\xbe\x34\x32\x1c\x59\xd9\x2d\xc6\xc9\xe9\x01\xf1\x91\xe6\x24\xe9\xea\xf3\x82\x64\x56\xbd\xd1\xa2\x33\x52\x13\x2b\xa8\x3d\xa7\x77\x43\x9c\xf0\x39\xa5\x2b\x5e\xa3\x72\xcf\x6b\x39\x82\x9b\xc6\xf2\xee\x59\xc0\x37\xd6\x1a\xbb\xac\xf4\xf4\xa1\x0e\xfb\x4b\x0e\x61\x54\x1b\xb1\x9f\x93\x1c\xd2\x6e\x26\x25\x8e\xd6\x67\x9c\xb9\xf0\x00\x49\xc8\x0f\xc5\x71\xfd\xe1\xdd\x15\x7c\x86\xad\x32\x35\x57\x1f\xde\x5d\x0d\xa1\x1b\x77\xd7\x37\x4d\x8b\xf7\x18\xc2\x65\x51\x8c\x3b\x6f\x8d\xa3\x10\xc6\xc5\x35\xa7\x36\x84\x18\xc1\xac\x5e\xbc\xc6\x09\x0f\x15\x5f\xe3\x15\x5c\xec\xb8\xea\xd1\xa5\x12\x12\x61\x7e\xed\xd1\xee\x61\x81\xe0\x23\x08\x39\x99\x47\xeb\x11\xe8\xac\x25\x00\x8b\xa9\x3e\xc5\x7a\xba\x02\xa4\x6f\xde\x59\x79\xcf\xed\x3e\x25\x6a\xda\x09\x21\xfa\x63\x40\x0d\x21\x63\x45\xb4\x5c\xe6\x15\xaf\x35\xd4\xf1\x97\xc9\x9f\xe6\xf9\x89\xec\xec\xe3\x9d\x32\xe2\x0a\x2d\x41\xfa\xe6\xde\xc3\x7a\x28\xb5\xf0\x19\x86\x8a\xf3\xde\xfc\x90\xca\x44\x08\x10\x5b\x15\x7e\x94\x5a\xc8\x86\x93\xb1\x10\x1b\x67\xde\x77\x1d\xda\x86\x3b\x9c\xcd\xe3\x23\x91\x11\xf7\x0c\xd9\xf3\xee\xfa\x77\xc8\x36\xbd\x75\xc6\xe6\xa9\x40\xa0\xcd\x40\x70\xe2\x39\x99\xed\x56\xc5\x41\xc0\x18\x45\xb2\xcb\x80\x24\xc5\xf5\x28\x6e\xe9\x5e\x95\x64\x7b\x1c\x96\xc6\xca\xad\xd4\x5c\xe5\xa3\x16\xab\xab\xef\x71\x63\x2c\xc6\xf1\x21\x46\x81\xd4\xdb\x4b\x56\xd4\xd5\x21\xe6\xee\x62\xcc\xa5\x68\xfd\x51\xba\x26\xd6\x3c\x14\x43\x61\x09\x21\x06\xbe\xf7\x17\x77\xd1\xdf\x74\xaf\xc6\x9f\x10\xca\xff\xff\xd1\x1b\xfa\x36\x46\xd3\x63\xd1\x24\x99\x6f\x4f\x0f\xbd\x3e\xc4\x65\x4a\x95\x54\x7a\x87\x63\x61\x3d\xfc\xae\x7f\xe6\x5d\xaa\xfe\xd9\x5f\x4b\x9e\x07\xf9\x97\x12\x48\x8d\x34\xfe\xc3\x04\x52\x0e\x5f\x7a\xbe\xc0\x0d\xef\x15\x65\x95\x36\x1a\xff\x79\xb6\x7e\xa1\x00\x4e\x83\xca\x3a\xf6\x98\xa1\xc5\xac\x7f\x72\xbf\xa3\x35\x21\xfc\x82\xbb\x34\xa6\x24\x0f\x78\xef\xa4\x6e\xf0\x54\x31\x04\xe0\x5b\xf3\x85\x6a\xc8\xf1\x56\xa9\xa5\x9d\x73\xcb\x52\xb5\x19\xe6\xac\xc7\x65\x25\x35\x8f\x13\xdc\xe7\xde\xe5\xa5\xfc\x96\x5a\xed\x32\x1e\x2b\x16\x5a\x2d\x2b\xd2\x3c\xf3\x77\xe7\xb0\x87\x27\x9d\x80\xb0\x42\xc8\xdd\x61\xca\xff\x33\x00\x00\xff\xff\x65\x7b\xa9\x65\xbe\x0c\x00\x00")
|
||||
var _webUiTemplatesTargetsHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x56\x4d\x8f\xdb\x36\x13\xbe\xfb\x57\x0c\xf4\x1a\x2f\x5a\x20\xb2\x80\x00\xbd\x6c\x29\x15\x68\x1b\x20\x05\xb6\xc5\x36\x9b\xf4\xd0\x4b\x40\x89\x63\x8b\xbb\x5c\x52\x25\x47\x46\x0c\x86\xff\xbd\x20\x25\xf9\x63\xd7\xf2\xb6\xdb\x06\xbd\xc8\x26\x67\xe6\xe1\x3c\xc3\xf9\xa0\xf7\x02\xd7\x52\x23\x64\x2d\x72\x91\x85\xb0\x60\x4a\xea\x7b\xa0\x5d\x87\x65\x46\xf8\x89\x8a\xc6\xb9\x0c\x2c\xaa\x32\x73\xb4\x53\xe8\x5a\x44\xca\xa0\xb5\xb8\x2e\x33\xef\xa1\xe3\xd4\xde\x58\x5c\xcb\x4f\x10\x42\xe1\x88\x93\x6c\xa2\x4d\x41\xdc\x6e\x90\xdc\xaa\x71\xee\xbb\x6d\xe9\x3d\xd4\xbd\x54\xe2\x37\xb4\x4e\x1a\x0d\x21\x64\xd5\x82\xb9\xc6\xca\x8e\xc0\xd9\x66\x1e\xeb\xee\x00\x75\x37\x87\xc4\x8a\x01\xa9\x5a\x78\x8f\x5a\x84\xb0\x58\x1c\x98\x35\x46\x13\x6a\x8a\xe4\x00\x98\x90\x5b\x68\x14\x77\xae\x4c\x02\x2e\x35\xda\x7c\xad\x7a\x29\xb2\x6a\x01\x00\xc0\xda\xd7\x20\x45\x99\x8d\x87\x66\xd5\xfb\xe1\x0f\x2b\xda\xd7\x83\x06\x00\x23\x5e\x2b\x9c\x70\x86\x45\xfa\xe6\x8d\xd1\x02\xb5\x43\x31\xae\x6b\x63\x05\xda\xfd\xb2\x35\x5b\xb4\xd9\x04\x03\xe0\xbd\xe5\x7a\x83\xb0\xbc\x33\xf5\x2b\x58\x76\xc6\x28\xb8\x2a\x61\x35\x9c\x79\x63\x8c\x72\x90\xfc\x3e\x18\x2c\x5b\xe4\x8a\xda\x5d\xd4\xd3\xfd\xc3\xdb\x71\x95\x6c\x1f\xab\x92\x21\x9e\x00\x15\xea\x33\x1a\x91\x88\x9d\x58\xdc\x99\xfa\x63\x4c\x02\xb4\xde\xcb\x35\x28\x82\xfd\x49\x03\x4e\x08\x20\xa2\xb3\x76\x8c\xf1\x11\x8d\x09\x4c\x40\x63\x94\xeb\xb8\x2e\xb3\x6f\x9e\x88\x01\x98\x9c\x0e\x93\x8d\xd1\x79\xd3\xe2\xd6\x1a\x9d\xf7\x5d\xbc\x41\x59\x31\x9e\x02\x7f\x67\xea\xdc\xfb\x18\x91\x10\xa6\x44\xfb\xdf\xc9\x66\x35\xfd\x83\xaf\x0e\xf1\x08\xa1\x98\x18\x87\x00\x7d\xf7\x35\x2b\xf8\x13\x0f\x0b\x12\xa7\x7b\xac\x20\x5b\x5d\x0a\x89\x40\xe2\x52\xb9\x73\x64\xcf\x10\x7c\x69\x5e\x38\xb2\xb2\x9b\xcd\x92\xe3\x03\xe2\x15\x9d\x93\x24\xc7\xcf\x0b\x92\x59\xf5\x46\x8b\xce\x48\x4d\xac\xa0\xf6\x92\xde\x2d\x71\xc2\xe7\x94\xae\x79\x8d\xca\x3d\xaf\xe5\x08\x6e\x1b\xcb\xbb\x67\x01\xdf\x58\x6b\xec\xbc\xd2\xd3\x6b\xda\xef\xcf\x05\x84\x51\x6d\xc4\xee\x9c\x64\x5f\x74\x67\x0a\xe2\x60\x7d\x21\x98\x33\x17\x90\x84\x7c\xdf\x1a\x57\x1f\xde\x5d\xc3\x67\xd8\x28\x53\x73\xf5\xe1\xdd\xf5\x90\xb8\x71\x77\x75\xdb\xb4\xf8\x80\x21\x5c\x15\xc5\xb8\xf3\xd6\x38\x0a\x61\x5c\xdc\x70\x6a\x43\x88\xf9\xcb\xea\x59\x37\x8e\x78\xa8\x78\x1b\xaf\x60\xb9\xe5\xaa\x47\x97\x1a\x48\x84\xf9\xb5\x47\xbb\x83\x19\x82\x8f\x20\xe4\x64\x1e\xad\x47\xa0\x8b\x96\x00\x2c\x16\xfa\x94\xeb\xc9\x05\x48\xdf\xbc\xb3\xf2\x81\xdb\x5d\x2a\xd3\xb4\x13\x42\x8c\xc7\x80\x1a\x42\xc6\x8a\x68\x39\xcf\x2b\xba\x35\x74\xf1\x97\xc9\x9f\x56\xf9\x91\xec\xe2\xe5\x1d\x33\xe2\x0a\x2d\x41\xfa\xe6\xde\xc3\x6a\x68\xb4\xf0\x19\x86\x7e\xf3\xde\xfc\x10\xf5\x20\x04\x88\x83\x0a\x3f\x4a\x2d\x64\xc3\xc9\x58\x88\x63\x33\xef\xbb\x0e\x6d\xc3\x1d\x9e\xad\xe3\x03\x91\x11\xf7\x02\xd9\xcb\xe1\xfa\x77\xc8\x36\xbd\x75\xc6\xe6\xa9\x41\xa0\xcd\x40\x70\xe2\x39\x99\xcd\x46\xc5\x67\x80\x31\x8a\x64\x97\x01\x49\x8a\xeb\x51\xdc\xd2\x83\x2a\xc9\xf6\x38\x2c\x8d\x95\x1b\xa9\xb9\xca\x47\x2d\x56\x57\xdf\xe3\xda\x58\x8c\x8f\x87\x98\x05\x52\x6f\xae\x58\x51\x57\xfb\x9c\xbb\x8f\x39\x97\xb2\xf5\x47\xe9\x9a\xd8\xf3\x50\x0c\x8d\x25\x84\x98\xf8\xde\x2f\x71\x3b\xe4\x63\x0c\x3b\x3d\xa8\x58\x21\xcb\xfb\x10\xca\xff\xff\xd1\x1b\xfa\x36\x29\x84\x30\x2d\xce\x4f\xa5\xd3\x70\x0f\x09\x99\x6a\x24\xf5\xdc\xe1\x3c\x58\x0d\xbf\xab\x9f\x79\x07\xb1\xed\x67\x7f\xad\x6a\x4e\x0a\x2f\x79\xaa\x46\xff\xff\xc3\xca\x51\x0e\x5f\x7a\xbe\xc0\x35\xef\x15\x65\x95\x36\x1a\xff\x79\x99\x7e\xa1\xcc\x4d\xef\x93\x55\x1c\x2e\xc3\x6c\x59\xfd\xe4\x7e\x47\x6b\x42\xf8\x05\xb7\xe9\x75\x92\x22\xe0\xbd\x93\xba\xc1\x63\xc5\x10\x80\x6f\xcc\x17\x6a\x1e\x07\xaf\xd2\x2c\xbb\x14\x96\xb9\x36\x33\x3c\xaf\x1e\xf7\x93\x34\x35\x8e\x70\x9f\xbb\x97\x97\xf2\x9b\x9b\xb1\xf3\x78\xac\x98\x99\xb1\xac\x48\x0f\x99\xbf\xfb\xfc\x3a\x3d\xe9\x08\x84\x15\x42\x6e\xf7\x8f\xfb\x3f\x03\x00\x00\xff\xff\xab\xec\xff\xc4\xb5\x0c\x00\x00")
|
||||
|
||||
func webUiTemplatesTargetsHtmlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
|
@ -263,7 +263,7 @@ func webUiTemplatesTargetsHtml() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "web/ui/templates/targets.html", size: 3262, mode: os.FileMode(420), modTime: time.Unix(1502370142, 0)}
|
||||
info := bindataFileInfo{name: "web/ui/templates/targets.html", size: 3253, mode: os.FileMode(420), modTime: time.Unix(1504524566, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -343,7 +343,7 @@ func webUiStaticCssPrometheusCss() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "web/ui/static/css/prometheus.css", size: 322, mode: os.FileMode(420), modTime: time.Unix(1502369610, 0)}
|
||||
info := bindataFileInfo{name: "web/ui/static/css/prometheus.css", size: 322, mode: os.FileMode(420), modTime: time.Unix(1502452035, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -363,7 +363,7 @@ func webUiStaticCssTargetsCss() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "web/ui/static/css/targets.css", size: 182, mode: os.FileMode(420), modTime: time.Unix(1502369610, 0)}
|
||||
info := bindataFileInfo{name: "web/ui/static/css/targets.css", size: 182, mode: os.FileMode(420), modTime: time.Unix(1502452035, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -443,7 +443,7 @@ func webUiStaticJsGraphJs() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "web/ui/static/js/graph.js", size: 27439, mode: os.FileMode(420), modTime: time.Unix(1502369610, 0)}
|
||||
info := bindataFileInfo{name: "web/ui/static/js/graph.js", size: 27439, mode: os.FileMode(420), modTime: time.Unix(1502452035, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -503,7 +503,7 @@ func webUiStaticJsTargetsJs() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "web/ui/static/js/targets.js", size: 983, mode: os.FileMode(420), modTime: time.Unix(1502369610, 0)}
|
||||
info := bindataFileInfo{name: "web/ui/static/js/targets.js", size: 983, mode: os.FileMode(420), modTime: time.Unix(1502452035, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -583,7 +583,7 @@ func webUiStaticVendorBootstrap331FontsGlyphiconsHalflingsRegularSvg() (*asset,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/fonts/glyphicons-halflings-regular.svg", size: 62926, mode: os.FileMode(420), modTime: time.Unix(1461244866, 0)}
|
||||
info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/fonts/glyphicons-halflings-regular.svg", size: 62926, mode: os.FileMode(420), modTime: time.Unix(1502447168, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://prometheus.io" target="_blank">Help</a>
|
||||
<a href="https://prometheus.io/docs" target="_blank">Help</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<i class="icon-chevron-up"></i><a id="job-{{$job}}" href="#job-{{$job}}">{{$job}} ({{$healthy}}/{{$total}} up)</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class = "job_details">
|
||||
<tr class="job_details">
|
||||
<td>
|
||||
<table class="table table-condensed table-bordered table-striped table-hover">
|
||||
<thead>
|
||||
|
@ -44,7 +44,7 @@
|
|||
</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}}">
|
||||
<span class="cursor-pointer" data-toggle="tooltip" title="" data-html=true data-original-title="<b>Before relabeling:</b>{{range $k, $v := .DiscoveredLabels}}<br>{{$ev := $v | html}}{{$k}}="{{$ev}}"{{end}}">
|
||||
{{$labels := stripLabels .Labels.Map "job"}}
|
||||
{{range $label, $value := $labels}}
|
||||
<span class="label label-primary">{{$label}}="{{$value}}"</span>
|
||||
|
|
48
web/web.go
48
web/web.go
|
@ -21,6 +21,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -80,6 +81,7 @@ type Handler struct {
|
|||
quitCh chan struct{}
|
||||
reloadCh chan chan error
|
||||
options *Options
|
||||
config *config.Config
|
||||
configString string
|
||||
versionInfo *PrometheusVersion
|
||||
birth time.Time
|
||||
|
@ -93,13 +95,12 @@ type Handler struct {
|
|||
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.
|
||||
// ApplyConfig updates the config field of the Handler struct
|
||||
func (h *Handler) ApplyConfig(conf *config.Config) error {
|
||||
h.mtx.Lock()
|
||||
defer h.mtx.Unlock()
|
||||
|
||||
h.externalLabels = conf.GlobalConfig.ExternalLabels
|
||||
h.configString = conf.String()
|
||||
h.config = conf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -166,11 +167,18 @@ func New(o *Options) *Handler {
|
|||
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)
|
||||
h.apiV1 = api_v1.NewAPI(h.queryEngine, h.storage, h.targetManager, h.notifier,
|
||||
func() config.Config {
|
||||
h.mtx.RLock()
|
||||
defer h.mtx.RUnlock()
|
||||
return *h.config
|
||||
},
|
||||
)
|
||||
|
||||
if o.RoutePrefix != "/" {
|
||||
// If the prefix is missing for the root path, prepend it.
|
||||
|
@ -192,7 +200,7 @@ func New(o *Options) *Handler {
|
|||
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("/config", readyf(instrf("config", h.serveConfig)))
|
||||
router.Get("/rules", readyf(instrf("rules", h.rules)))
|
||||
router.Get("/targets", readyf(instrf("targets", h.targets)))
|
||||
router.Get("/version", readyf(instrf("version", h.version)))
|
||||
|
@ -235,8 +243,8 @@ func New(o *Options) *Handler {
|
|||
w.Write([]byte("Only POST requests allowed"))
|
||||
})
|
||||
|
||||
router.Get("/debug/*subpath", readyf(http.DefaultServeMux.ServeHTTP))
|
||||
router.Post("/debug/*subpath", readyf(http.DefaultServeMux.ServeHTTP))
|
||||
router.Get("/debug/*subpath", readyf(serveDebug))
|
||||
router.Post("/debug/*subpath", readyf(serveDebug))
|
||||
|
||||
router.Get("/-/healthy", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
@ -264,6 +272,26 @@ func setCORS(w http.ResponseWriter) {
|
|||
}
|
||||
}
|
||||
|
||||
func serveDebug(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
subpath := route.Param(ctx, "subpath")
|
||||
|
||||
// Based off paths from init() in golang.org/src/net/http/pprof/pprof.go
|
||||
if subpath == "/pprof/" {
|
||||
pprof.Index(w, req)
|
||||
} else if subpath == "/pprof/cmdline" {
|
||||
pprof.Cmdline(w, req)
|
||||
} else if subpath == "/pprof/profile" {
|
||||
pprof.Profile(w, req)
|
||||
} else if subpath == "/pprof/symbol" {
|
||||
pprof.Symbol(w, req)
|
||||
} else if subpath == "/pprof/trace" {
|
||||
pprof.Trace(w, req)
|
||||
} else {
|
||||
http.NotFound(w, req)
|
||||
}
|
||||
}
|
||||
|
||||
func serveStaticAsset(w http.ResponseWriter, req *http.Request) {
|
||||
fp := route.Param(req.Context(), "filepath")
|
||||
fp = filepath.Join("web/ui/static", fp)
|
||||
|
@ -489,11 +517,11 @@ func (h *Handler) flags(w http.ResponseWriter, r *http.Request) {
|
|||
h.executeTemplate(w, "flags.html", h.flagsMap)
|
||||
}
|
||||
|
||||
func (h *Handler) config(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Handler) serveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
h.mtx.RLock()
|
||||
defer h.mtx.RUnlock()
|
||||
|
||||
h.executeTemplate(w, "config.html", h.configString)
|
||||
h.executeTemplate(w, "config.html", h.config.String())
|
||||
}
|
||||
|
||||
func (h *Handler) rules(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
Loading…
Reference in a new issue