mirror of https://github.com/prometheus/prometheus
Richard Kiene
8 years ago
6 changed files with 440 additions and 0 deletions
@ -0,0 +1,169 @@
|
||||
// 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 triton |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"time" |
||||
|
||||
"github.com/prometheus/client_golang/prometheus" |
||||
"github.com/prometheus/common/log" |
||||
"github.com/prometheus/common/model" |
||||
"github.com/prometheus/prometheus/config" |
||||
"github.com/prometheus/prometheus/util/httputil" |
||||
"golang.org/x/net/context" |
||||
) |
||||
|
||||
const ( |
||||
tritonLabel = model.MetaLabelPrefix + "triton_" |
||||
tritonLabelMachineId = tritonLabel + "machine_id" |
||||
tritonLabelMachineAlias = tritonLabel + "machine_alias" |
||||
tritonLabelMachineImage = tritonLabel + "machine_image" |
||||
tritonLabelServerId = tritonLabel + "server_id" |
||||
namespace = "prometheus" |
||||
) |
||||
|
||||
var ( |
||||
refreshFailuresCount = prometheus.NewCounter( |
||||
prometheus.CounterOpts{ |
||||
Name: "prometheus_sd_triton_refresh_failures_total", |
||||
Help: "The number of Triton-SD scrape failures.", |
||||
}) |
||||
refreshDuration = prometheus.NewSummary( |
||||
prometheus.SummaryOpts{ |
||||
Name: "prometheus_sd_triton_refresh_duration_seconds", |
||||
Help: "The duration of a Triton-SD refresh in seconds.", |
||||
}) |
||||
) |
||||
|
||||
func init() { |
||||
prometheus.MustRegister(refreshFailuresCount) |
||||
prometheus.MustRegister(refreshDuration) |
||||
} |
||||
|
||||
type DiscoveryResponse struct { |
||||
Containers []struct { |
||||
ServerUUID string `json:"server_uuid"` |
||||
VMAlias string `json:"vm_alias"` |
||||
VMImageUUID string `json:"vm_image_uuid"` |
||||
VMUUID string `json:"vm_uuid"` |
||||
} `json:"containers"` |
||||
} |
||||
|
||||
// Discovery periodically performs Triton-SD requests. It implements
|
||||
// the TargetProvider interface.
|
||||
type Discovery struct { |
||||
client *http.Client |
||||
interval time.Duration |
||||
logger log.Logger |
||||
sdConfig *config.TritonSDConfig |
||||
} |
||||
|
||||
// New returns a new Discovery which periodically refreshes its targets.
|
||||
func New(logger log.Logger, conf *config.TritonSDConfig) (*Discovery, error) { |
||||
tls, err := httputil.NewTLSConfig(conf.TLSConfig) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
transport := &http.Transport{TLSClientConfig: tls} |
||||
client := &http.Client{Transport: transport} |
||||
|
||||
return &Discovery{ |
||||
client: client, |
||||
interval: time.Duration(conf.RefreshInterval), |
||||
logger: logger, |
||||
sdConfig: conf, |
||||
}, nil |
||||
} |
||||
|
||||
// Run implements the TargetProvider interface.
|
||||
func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) { |
||||
defer close(ch) |
||||
|
||||
ticker := time.NewTicker(d.interval) |
||||
defer ticker.Stop() |
||||
|
||||
// Get an initial set right away.
|
||||
tg, err := d.refresh() |
||||
if err != nil { |
||||
d.logger.With("err", err).Error("Refreshing targets failed") |
||||
} else { |
||||
ch <- []*config.TargetGroup{tg} |
||||
} |
||||
|
||||
for { |
||||
select { |
||||
case <-ticker.C: |
||||
tg, err := d.refresh() |
||||
if err != nil { |
||||
d.logger.With("err", err).Error("Refreshing targets failed") |
||||
} else { |
||||
ch <- []*config.TargetGroup{tg} |
||||
} |
||||
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() |
||||
} |
||||
}() |
||||
|
||||
var endpoint = fmt.Sprintf("https://%s:%d/v%d/discover", d.sdConfig.Endpoint, d.sdConfig.Port, d.sdConfig.Version) |
||||
tg = &config.TargetGroup{ |
||||
Source: endpoint, |
||||
} |
||||
|
||||
resp, err := d.client.Get(endpoint) |
||||
if err != nil { |
||||
return tg, fmt.Errorf("an error occurred when requesting targets from the discovery endpoint. %s", err) |
||||
} |
||||
|
||||
defer resp.Body.Close() |
||||
|
||||
data, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return tg, fmt.Errorf("an error occurred when reading the response body. %s", err) |
||||
} |
||||
|
||||
dr := DiscoveryResponse{} |
||||
err = json.Unmarshal(data, &dr) |
||||
if err != nil { |
||||
return tg, fmt.Errorf("an error occurred unmarshaling the disovery response json. %s", err) |
||||
} |
||||
|
||||
for _, container := range dr.Containers { |
||||
labels := model.LabelSet{ |
||||
tritonLabelMachineId: model.LabelValue(container.VMUUID), |
||||
tritonLabelMachineAlias: model.LabelValue(container.VMAlias), |
||||
tritonLabelMachineImage: model.LabelValue(container.VMImageUUID), |
||||
tritonLabelServerId: model.LabelValue(container.ServerUUID), |
||||
} |
||||
addr := fmt.Sprintf("%s.%s:%d", container.VMUUID, d.sdConfig.DNSSuffix, d.sdConfig.Port) |
||||
labels[model.AddressLabel] = model.LabelValue(addr) |
||||
tg.Targets = append(tg.Targets, labels) |
||||
} |
||||
|
||||
return tg, nil |
||||
} |
@ -0,0 +1,178 @@
|
||||
// Copyright 2016 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package triton |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net" |
||||
"net/http" |
||||
"net/http/httptest" |
||||
"net/url" |
||||
"strconv" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/prometheus/common/log" |
||||
"github.com/prometheus/common/model" |
||||
"github.com/prometheus/prometheus/config" |
||||
"github.com/stretchr/testify/assert" |
||||
"golang.org/x/net/context" |
||||
) |
||||
|
||||
var ( |
||||
conf = config.TritonSDConfig{ |
||||
Account: "testAccount", |
||||
DNSSuffix: "triton.example.com", |
||||
Endpoint: "127.0.0.1", |
||||
Port: 443, |
||||
Version: 1, |
||||
RefreshInterval: 1, |
||||
TLSConfig: config.TLSConfig{InsecureSkipVerify: true}, |
||||
} |
||||
badconf = config.TritonSDConfig{ |
||||
Account: "badTestAccount", |
||||
DNSSuffix: "bad.triton.example.com", |
||||
Endpoint: "127.0.0.1", |
||||
Port: 443, |
||||
Version: 1, |
||||
RefreshInterval: 1, |
||||
TLSConfig: config.TLSConfig{ |
||||
InsecureSkipVerify: false, |
||||
KeyFile: "shouldnotexist.key", |
||||
CAFile: "shouldnotexist.ca", |
||||
CertFile: "shouldnotexist.cert", |
||||
}, |
||||
} |
||||
logger = log.Base() |
||||
) |
||||
|
||||
func TestTritonSDNew(t *testing.T) { |
||||
td, err := New(logger, &conf) |
||||
assert.Nil(t, err) |
||||
assert.NotNil(t, td) |
||||
assert.NotNil(t, td.client) |
||||
assert.NotNil(t, td.interval) |
||||
assert.NotNil(t, td.logger) |
||||
assert.Equal(t, logger, td.logger, "td.logger equals logger") |
||||
assert.NotNil(t, td.sdConfig) |
||||
assert.Equal(t, conf.Account, td.sdConfig.Account) |
||||
assert.Equal(t, conf.DNSSuffix, td.sdConfig.DNSSuffix) |
||||
assert.Equal(t, conf.Endpoint, td.sdConfig.Endpoint) |
||||
assert.Equal(t, conf.Port, td.sdConfig.Port) |
||||
} |
||||
|
||||
func TestTritonSDNewBadConfig(t *testing.T) { |
||||
td, err := New(logger, &badconf) |
||||
assert.NotNil(t, err) |
||||
assert.Nil(t, td) |
||||
} |
||||
|
||||
func TestTritonSDRun(t *testing.T) { |
||||
var ( |
||||
td, err = New(logger, &conf) |
||||
ch = make(chan []*config.TargetGroup) |
||||
ctx, cancel = context.WithCancel(context.Background()) |
||||
) |
||||
|
||||
assert.Nil(t, err) |
||||
assert.NotNil(t, td) |
||||
|
||||
go td.Run(ctx, ch) |
||||
|
||||
select { |
||||
case <-time.After(60 * time.Millisecond): |
||||
// Expected.
|
||||
case tgs := <-ch: |
||||
t.Fatalf("Unexpected target groups in triton discovery: %s", tgs) |
||||
} |
||||
|
||||
cancel() |
||||
} |
||||
|
||||
func TestTritonSDRefreshNoTargets(t *testing.T) { |
||||
tgts := testTritonSDRefresh(t, "{\"containers\":[]}") |
||||
assert.Nil(t, tgts) |
||||
} |
||||
|
||||
func TestTritonSDRefreshMultipleTargets(t *testing.T) { |
||||
var ( |
||||
dstr = `{"containers":[ |
||||
{ |
||||
"server_uuid":"44454c4c-5000-104d-8037-b7c04f5a5131", |
||||
"vm_alias":"server01", |
||||
"vm_image_uuid":"7b27a514-89d7-11e6-bee6-3f96f367bee7", |
||||
"vm_uuid":"ad466fbf-46a2-4027-9b64-8d3cdb7e9072" |
||||
}, |
||||
{ |
||||
"server_uuid":"a5894692-bd32-4ca1-908a-e2dda3c3a5e6", |
||||
"vm_alias":"server02", |
||||
"vm_image_uuid":"a5894692-bd32-4ca1-908a-e2dda3c3a5e6", |
||||
"vm_uuid":"7b27a514-89d7-11e6-bee6-3f96f367bee7" |
||||
}] |
||||
}` |
||||
) |
||||
|
||||
tgts := testTritonSDRefresh(t, dstr) |
||||
assert.NotNil(t, tgts) |
||||
assert.Equal(t, 2, len(tgts)) |
||||
} |
||||
|
||||
func TestTritonSDRefreshNoServer(t *testing.T) { |
||||
var ( |
||||
td, err = New(logger, &conf) |
||||
) |
||||
assert.Nil(t, err) |
||||
assert.NotNil(t, td) |
||||
|
||||
tg, rerr := td.refresh() |
||||
assert.NotNil(t, rerr) |
||||
assert.Contains(t, rerr.Error(), "an error occurred when requesting targets from the discovery endpoint.") |
||||
assert.NotNil(t, tg) |
||||
assert.Nil(t, tg.Targets) |
||||
} |
||||
|
||||
func testTritonSDRefresh(t *testing.T, dstr string) []model.LabelSet { |
||||
var ( |
||||
td, err = New(logger, &conf) |
||||
s = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||
fmt.Fprintln(w, dstr) |
||||
})) |
||||
) |
||||
|
||||
defer s.Close() |
||||
|
||||
u, uperr := url.Parse(s.URL) |
||||
assert.Nil(t, uperr) |
||||
assert.NotNil(t, u) |
||||
|
||||
host, strport, sherr := net.SplitHostPort(u.Host) |
||||
assert.Nil(t, sherr) |
||||
assert.NotNil(t, host) |
||||
assert.NotNil(t, strport) |
||||
|
||||
port, atoierr := strconv.Atoi(strport) |
||||
assert.Nil(t, atoierr) |
||||
assert.NotNil(t, port) |
||||
|
||||
td.sdConfig.Port = port |
||||
|
||||
assert.Nil(t, err) |
||||
assert.NotNil(t, td) |
||||
|
||||
tg, err := td.refresh() |
||||
assert.Nil(t, err) |
||||
assert.NotNil(t, tg) |
||||
|
||||
return tg.Targets |
||||
} |
Loading…
Reference in new issue