Merge branch 'master' into dev-2.0

This commit is contained in:
Fabian Reinartz 2017-09-04 14:09:21 +02:00
commit 87918f3097
39 changed files with 1294 additions and 298 deletions

View file

@ -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?**

View file

@ -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")
}

View file

@ -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) {

View file

@ -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 {

View file

@ -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

View file

@ -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{}

View file

@ -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:
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

View file

@ -0,0 +1,9 @@
[
{
"targets": ["localhost:9090", "example.org:443"],
"labels": {
"foo": "bar"
}
},
null
]

View file

@ -0,0 +1,5 @@
- targets: ['localhost:9090', 'example.org:443']
labels:
foo: bar
- null

View 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
}

View 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"))
}

View 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
}

View file

@ -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, log.Base())
}
return NewDiscovery(&conf)
}
func (s *OpenstackSDInstanceTestSuite) TestOpenstackSDInstanceRefresh() {
instance, _ := s.openstackAuthSuccess()
tg, err := instance.refresh()
func (s *OpenstackSDTestSuite) TestOpenstackSDRefresh() {
d, _ := s.openstackAuthSuccess()
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"))
}

View file

@ -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",

View file

@ -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
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")
}
}
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
}
}
}
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
}

View file

@ -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
}

View file

@ -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"),
},

View file

@ -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 {

View file

@ -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,

View file

@ -0,0 +1,3 @@
// Package hypervisors gives information and control of the os-hypervisors
// portion of the compute API
package hypervisors

View 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)}
})
}

View 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
}

View file

@ -0,0 +1,7 @@
package hypervisors
import "github.com/gophercloud/gophercloud"
func hypervisorsListDetailURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("os-hypervisors", "detail")
}

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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)

View file

@ -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
}

View file

@ -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
View file

@ -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=",

View file

@ -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"
@ -104,16 +105,18 @@ type API struct {
alertmanagerRetriever alertmanagerRetriever
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,
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)

View file

@ -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
@ -93,6 +103,7 @@ func TestEndpoints(t *testing.T) {
targetRetriever: tr,
alertmanagerRetriever: ar,
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 {

View file

@ -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] = ""
}

View file

@ -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",
)))

View file

@ -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
}

View file

@ -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>

View file

@ -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}}=&quot;{{$v | html | html}}&quot;{{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}}=&quot;{{$ev}}&quot;{{end}}">
{{$labels := stripLabels .Labels.Map "job"}}
{{range $label, $value := $labels}}
<span class="label label-primary">{{$label}}="{{$value}}"</span>

View file

@ -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
}
@ -167,10 +168,17 @@ func New(o *Options) *Handler {
notifier: o.Notifier,
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) {