parent
cfd4e05c9e
commit
7c0d5ae4e7
@ -0,0 +1,5 @@ |
||||
scrape_configs: |
||||
|
||||
- job_name: eureka |
||||
eureka_sd_configs: |
||||
- server: eureka.com |
||||
@ -0,0 +1,5 @@ |
||||
scrape_configs: |
||||
|
||||
- job_name: eureka |
||||
eureka_sd_configs: |
||||
- server: |
||||
@ -0,0 +1,110 @@ |
||||
// Copyright 2020 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 eureka |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/xml" |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"net/http" |
||||
|
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
type Applications struct { |
||||
VersionsDelta int `xml:"versions__delta"` |
||||
AppsHashcode string `xml:"apps__hashcode"` |
||||
Applications []Application `xml:"application"` |
||||
} |
||||
|
||||
type Application struct { |
||||
Name string `xml:"name"` |
||||
Instances []Instance `xml:"instance"` |
||||
} |
||||
|
||||
type Port struct { |
||||
Port int `xml:",chardata"` |
||||
Enabled bool `xml:"enabled,attr"` |
||||
} |
||||
|
||||
type Instance struct { |
||||
HostName string `xml:"hostName"` |
||||
HomePageURL string `xml:"homePageUrl"` |
||||
StatusPageURL string `xml:"statusPageUrl"` |
||||
HealthCheckURL string `xml:"healthCheckUrl"` |
||||
App string `xml:"app"` |
||||
IPAddr string `xml:"ipAddr"` |
||||
VipAddress string `xml:"vipAddress"` |
||||
SecureVipAddress string `xml:"secureVipAddress"` |
||||
Status string `xml:"status"` |
||||
Port *Port `xml:"port"` |
||||
SecurePort *Port `xml:"securePort"` |
||||
DataCenterInfo *DataCenterInfo `xml:"dataCenterInfo"` |
||||
Metadata *MetaData `xml:"metadata"` |
||||
IsCoordinatingDiscoveryServer bool `xml:"isCoordinatingDiscoveryServer"` |
||||
LastUpdatedTimestamp int `xml:"lastUpdatedTimestamp"` |
||||
LastDirtyTimestamp int `xml:"lastDirtyTimestamp"` |
||||
ActionType string `xml:"actionType"` |
||||
CountryID int `xml:"countryId"` |
||||
InstanceID string `xml:"instanceId"` |
||||
} |
||||
|
||||
type MetaData struct { |
||||
Items []Tag `xml:",any"` |
||||
} |
||||
|
||||
type Tag struct { |
||||
XMLName xml.Name |
||||
Content string `xml:",innerxml"` |
||||
} |
||||
|
||||
type DataCenterInfo struct { |
||||
Name string `xml:"name"` |
||||
Class string `xml:"class,attr"` |
||||
Metadata *MetaData `xml:"metadata"` |
||||
} |
||||
|
||||
const appListPath string = "/apps" |
||||
|
||||
func fetchApps(ctx context.Context, server string, client *http.Client) (*Applications, error) { |
||||
url := fmt.Sprintf("%s%s", server, appListPath) |
||||
|
||||
request, err := http.NewRequest("GET", url, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
request = request.WithContext(ctx) |
||||
|
||||
resp, err := client.Do(request) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer func() { |
||||
io.Copy(ioutil.Discard, resp.Body) |
||||
resp.Body.Close() |
||||
}() |
||||
|
||||
if resp.StatusCode/100 != 2 { |
||||
return nil, errors.Errorf("non 2xx status '%d' response during eureka service discovery", resp.StatusCode) |
||||
} |
||||
|
||||
var apps Applications |
||||
err = xml.NewDecoder(resp.Body).Decode(&apps) |
||||
if err != nil { |
||||
return nil, errors.Wrapf(err, "%q", url) |
||||
} |
||||
return &apps, nil |
||||
} |
||||
@ -0,0 +1,213 @@ |
||||
// Copyright 2020 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 eureka |
||||
|
||||
import ( |
||||
"context" |
||||
"io" |
||||
"net/http" |
||||
"net/http/httptest" |
||||
"testing" |
||||
|
||||
"github.com/prometheus/prometheus/util/testutil" |
||||
) |
||||
|
||||
func TestFetchApps(t *testing.T) { |
||||
appsXML := `<applications> |
||||
<versions__delta>1</versions__delta> |
||||
<apps__hashcode>UP_4_</apps__hashcode> |
||||
<application> |
||||
<name>CONFIG-SERVICE</name> |
||||
<instance> |
||||
<instanceId>config-service001.test.com:config-service:8080</instanceId> |
||||
<hostName>config-service001.test.com</hostName> |
||||
<app>CONFIG-SERVICE</app> |
||||
<ipAddr>192.133.83.31</ipAddr> |
||||
<status>UP</status> |
||||
<overriddenstatus>UNKNOWN</overriddenstatus> |
||||
<port enabled="true">8080</port> |
||||
<securePort enabled="false">8080</securePort> |
||||
<countryId>1</countryId> |
||||
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"> |
||||
<name>MyOwn</name> |
||||
</dataCenterInfo> |
||||
<leaseInfo> |
||||
<renewalIntervalInSecs>30</renewalIntervalInSecs> |
||||
<durationInSecs>90</durationInSecs> |
||||
<registrationTimestamp>1596003469304</registrationTimestamp> |
||||
<lastRenewalTimestamp>1596110179310</lastRenewalTimestamp> |
||||
<evictionTimestamp>0</evictionTimestamp> |
||||
<serviceUpTimestamp>1547190033103</serviceUpTimestamp> |
||||
</leaseInfo> |
||||
<metadata> |
||||
<instanceId>config-service001.test.com:config-service:8080</instanceId> |
||||
</metadata> |
||||
<homePageUrl>http://config-service001.test.com:8080/</homePageUrl>
|
||||
<statusPageUrl>http://config-service001.test.com:8080/info</statusPageUrl>
|
||||
<healthCheckUrl>http://config-service001.test.com 8080/health</healthCheckUrl>
|
||||
<vipAddress>config-service</vipAddress> |
||||
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer> |
||||
<lastUpdatedTimestamp>1596003469304</lastUpdatedTimestamp> |
||||
<lastDirtyTimestamp>1596003469304</lastDirtyTimestamp> |
||||
<actionType>ADDED</actionType> |
||||
</instance> |
||||
<instance> |
||||
<instanceId>config-service002.test.com:config-service:8080</instanceId> |
||||
<hostName>config-service002.test.com</hostName> |
||||
<app>CONFIG-SERVICE</app> |
||||
<ipAddr>192.133.83.31</ipAddr> |
||||
<status>UP</status> |
||||
<overriddenstatus>UNKNOWN</overriddenstatus> |
||||
<port enabled="true">8080</port> |
||||
<securePort enabled="false">8080</securePort> |
||||
<countryId>1</countryId> |
||||
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"> |
||||
<name>MyOwn</name> |
||||
</dataCenterInfo> |
||||
<leaseInfo> |
||||
<renewalIntervalInSecs>30</renewalIntervalInSecs> |
||||
<durationInSecs>90</durationInSecs> |
||||
<registrationTimestamp>1596003469304</registrationTimestamp> |
||||
<lastRenewalTimestamp>1596110179310</lastRenewalTimestamp> |
||||
<evictionTimestamp>0</evictionTimestamp> |
||||
<serviceUpTimestamp>1547190033103</serviceUpTimestamp> |
||||
</leaseInfo> |
||||
<metadata> |
||||
<instanceId>config-service002.test.com:config-service:8080</instanceId> |
||||
</metadata> |
||||
<homePageUrl>http://config-service002.test.com:8080/</homePageUrl>
|
||||
<statusPageUrl>http://config-service002.test.com:8080/info</statusPageUrl>
|
||||
<healthCheckUrl>http://config-service002.test.com:8080/health</healthCheckUrl>
|
||||
<vipAddress>config-service</vipAddress> |
||||
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer> |
||||
<lastUpdatedTimestamp>1596003469304</lastUpdatedTimestamp> |
||||
<lastDirtyTimestamp>1596003469304</lastDirtyTimestamp> |
||||
<actionType>ADDED</actionType> |
||||
</instance> |
||||
</application> |
||||
<application> |
||||
<name>META-SERVICE</name> |
||||
<instance> |
||||
<instanceId>meta-service002.test.com:meta-service:8080</instanceId> |
||||
<hostName>meta-service002.test.com</hostName> |
||||
<app>META-SERVICE</app> |
||||
<ipAddr>192.133.87.237</ipAddr> |
||||
<status>UP</status> |
||||
<overriddenstatus>UNKNOWN</overriddenstatus> |
||||
<port enabled="true">8080</port> |
||||
<securePort enabled="false">443</securePort> |
||||
<countryId>1</countryId> |
||||
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"> |
||||
<name>MyOwn</name> |
||||
</dataCenterInfo> |
||||
<leaseInfo> |
||||
<renewalIntervalInSecs>30</renewalIntervalInSecs> |
||||
<durationInSecs>90</durationInSecs> |
||||
<registrationTimestamp>1535444352472</registrationTimestamp> |
||||
<lastRenewalTimestamp>1596110168846</lastRenewalTimestamp> |
||||
<evictionTimestamp>0</evictionTimestamp> |
||||
<serviceUpTimestamp>1535444352472</serviceUpTimestamp> |
||||
</leaseInfo> |
||||
<metadata> |
||||
<project>meta-service</project> |
||||
<management.port>8090</management.port> |
||||
</metadata> |
||||
<homePageUrl>http://meta-service002.test.com:8080/</homePageUrl>
|
||||
<statusPageUrl>http://meta-service002.test.com:8080/info</statusPageUrl>
|
||||
<healthCheckUrl>http://meta-service002.test.com:8080/health</healthCheckUrl>
|
||||
<vipAddress>meta-service</vipAddress> |
||||
<secureVipAddress>meta-service</secureVipAddress> |
||||
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer> |
||||
<lastUpdatedTimestamp>1535444352472</lastUpdatedTimestamp> |
||||
<lastDirtyTimestamp>1535444352398</lastDirtyTimestamp> |
||||
<actionType>ADDED</actionType> |
||||
</instance> |
||||
<instance> |
||||
<instanceId>meta-service001.test.com:meta-service:8080</instanceId> |
||||
<hostName>meta-service001.test.com</hostName> |
||||
<app>META-SERVICE</app> |
||||
<ipAddr>192.133.87.236</ipAddr> |
||||
<status>UP</status> |
||||
<overriddenstatus>UNKNOWN</overriddenstatus> |
||||
<port enabled="true">8080</port> |
||||
<securePort enabled="false">443</securePort> |
||||
<countryId>1</countryId> |
||||
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"> |
||||
<name>MyOwn</name> |
||||
</dataCenterInfo> |
||||
<leaseInfo> |
||||
<renewalIntervalInSecs>30</renewalIntervalInSecs> |
||||
<durationInSecs>90</durationInSecs> |
||||
<registrationTimestamp>1535444352472</registrationTimestamp> |
||||
<lastRenewalTimestamp>1596110168846</lastRenewalTimestamp> |
||||
<evictionTimestamp>0</evictionTimestamp> |
||||
<serviceUpTimestamp>1535444352472</serviceUpTimestamp> |
||||
</leaseInfo> |
||||
<metadata> |
||||
<project>meta-service</project> |
||||
<management.port>8090</management.port> |
||||
</metadata> |
||||
<homePageUrl>http://meta-service001.test.com:8080/</homePageUrl>
|
||||
<statusPageUrl>http://meta-service001.test.com:8080/info</statusPageUrl>
|
||||
<healthCheckUrl>http://meta-service001.test.com:8080/health</healthCheckUrl>
|
||||
<vipAddress>meta-service</vipAddress> |
||||
<secureVipAddress>meta-service</secureVipAddress> |
||||
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer> |
||||
<lastUpdatedTimestamp>1535444352472</lastUpdatedTimestamp> |
||||
<lastDirtyTimestamp>1535444352398</lastDirtyTimestamp> |
||||
<actionType>ADDED</actionType> |
||||
</instance> |
||||
</application> |
||||
</applications>` |
||||
|
||||
// Simulate apps with a valid XML response.
|
||||
respHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
w.WriteHeader(http.StatusOK) |
||||
w.Header().Set("Content-Type", "application/xml") |
||||
io.WriteString(w, appsXML) |
||||
} |
||||
// Create a test server with mock HTTP handler.
|
||||
ts := httptest.NewServer(http.HandlerFunc(respHandler)) |
||||
defer ts.Close() |
||||
|
||||
apps, err := fetchApps(context.TODO(), ts.URL, &http.Client{}) |
||||
testutil.Ok(t, err) |
||||
|
||||
testutil.Equals(t, len(apps.Applications), 2) |
||||
testutil.Equals(t, apps.Applications[0].Name, "CONFIG-SERVICE") |
||||
testutil.Equals(t, apps.Applications[1].Name, "META-SERVICE") |
||||
|
||||
testutil.Equals(t, len(apps.Applications[1].Instances), 2) |
||||
testutil.Equals(t, apps.Applications[1].Instances[0].InstanceID, "meta-service002.test.com:meta-service:8080") |
||||
testutil.Equals(t, apps.Applications[1].Instances[0].Metadata.Items[0].XMLName.Local, "project") |
||||
testutil.Equals(t, apps.Applications[1].Instances[0].Metadata.Items[0].Content, "meta-service") |
||||
testutil.Equals(t, apps.Applications[1].Instances[0].Metadata.Items[1].XMLName.Local, "management.port") |
||||
testutil.Equals(t, apps.Applications[1].Instances[0].Metadata.Items[1].Content, "8090") |
||||
testutil.Equals(t, apps.Applications[1].Instances[1].InstanceID, "meta-service001.test.com:meta-service:8080") |
||||
} |
||||
|
||||
func Test500ErrorHttpResponse(t *testing.T) { |
||||
// Simulate 500 error.
|
||||
respHandler := func(w http.ResponseWriter, r *http.Request) { |
||||
w.WriteHeader(http.StatusInternalServerError) |
||||
w.Header().Set("Content-Type", "application/xml") |
||||
io.WriteString(w, ``) |
||||
} |
||||
// Create a test server with mock HTTP handler.
|
||||
ts := httptest.NewServer(http.HandlerFunc(respHandler)) |
||||
defer ts.Close() |
||||
|
||||
_, err := fetchApps(context.TODO(), ts.URL, &http.Client{}) |
||||
testutil.NotOk(t, err, "5xx HTTP response") |
||||
} |
||||
@ -0,0 +1,220 @@ |
||||
// Copyright 2020 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 eureka |
||||
|
||||
import ( |
||||
"context" |
||||
"net" |
||||
"net/http" |
||||
"net/url" |
||||
"strconv" |
||||
"time" |
||||
|
||||
"github.com/go-kit/kit/log" |
||||
"github.com/pkg/errors" |
||||
"github.com/prometheus/common/config" |
||||
"github.com/prometheus/common/model" |
||||
|
||||
"github.com/prometheus/prometheus/discovery" |
||||
"github.com/prometheus/prometheus/discovery/refresh" |
||||
"github.com/prometheus/prometheus/discovery/targetgroup" |
||||
"github.com/prometheus/prometheus/util/strutil" |
||||
) |
||||
|
||||
const ( |
||||
// metaLabelPrefix is the meta prefix used for all meta labels.
|
||||
// in this discovery.
|
||||
metaLabelPrefix = model.MetaLabelPrefix + "eureka_" |
||||
metaAppInstanceLabelPrefix = metaLabelPrefix + "app_instance_" |
||||
|
||||
appNameLabel = metaLabelPrefix + "app_name" |
||||
appInstanceHostNameLabel = metaAppInstanceLabelPrefix + "hostname" |
||||
appInstanceHomePageURLLabel = metaAppInstanceLabelPrefix + "homepage_url" |
||||
appInstanceStatusPageURLLabel = metaAppInstanceLabelPrefix + "statuspage_url" |
||||
appInstanceHealthCheckURLLabel = metaAppInstanceLabelPrefix + "healthcheck_url" |
||||
appInstanceIPAddrLabel = metaAppInstanceLabelPrefix + "ip_addr" |
||||
appInstanceVipAddressLabel = metaAppInstanceLabelPrefix + "vip_address" |
||||
appInstanceSecureVipAddressLabel = metaAppInstanceLabelPrefix + "secure_vip_address" |
||||
appInstanceStatusLabel = metaAppInstanceLabelPrefix + "status" |
||||
appInstancePortLabel = metaAppInstanceLabelPrefix + "port" |
||||
appInstancePortEnabledLabel = metaAppInstanceLabelPrefix + "port_enabled" |
||||
appInstanceSecurePortLabel = metaAppInstanceLabelPrefix + "secure_port" |
||||
appInstanceSecurePortEnabledLabel = metaAppInstanceLabelPrefix + "secure_port_enabled" |
||||
appInstanceDataCenterInfoNameLabel = metaAppInstanceLabelPrefix + "datacenterinfo_name" |
||||
appInstanceDataCenterInfoMetadataPrefix = metaAppInstanceLabelPrefix + "datacenterinfo_metadata_" |
||||
appInstanceCountryIDLabel = metaAppInstanceLabelPrefix + "country_id" |
||||
appInstanceIDLabel = metaAppInstanceLabelPrefix + "id" |
||||
appInstanceMetadataPrefix = metaAppInstanceLabelPrefix + "metadata_" |
||||
) |
||||
|
||||
// DefaultSDConfig is the default Eureka SD configuration.
|
||||
var DefaultSDConfig = SDConfig{ |
||||
RefreshInterval: model.Duration(30 * time.Second), |
||||
} |
||||
|
||||
func init() { |
||||
discovery.RegisterConfig(&SDConfig{}) |
||||
} |
||||
|
||||
// SDConfig is the configuration for applications running on Eureka.
|
||||
type SDConfig struct { |
||||
Server string `yaml:"server,omitempty"` |
||||
HTTPClientConfig config.HTTPClientConfig `yaml:",inline"` |
||||
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` |
||||
} |
||||
|
||||
// Name returns the name of the Config.
|
||||
func (*SDConfig) Name() string { return "eureka" } |
||||
|
||||
// NewDiscoverer returns a Discoverer for the Config.
|
||||
func (c *SDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Discoverer, error) { |
||||
return NewDiscovery(c, opts.Logger) |
||||
} |
||||
|
||||
// SetDirectory joins any relative file paths with dir.
|
||||
func (c *SDConfig) SetDirectory(dir string) { |
||||
c.HTTPClientConfig.SetDirectory(dir) |
||||
} |
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { |
||||
*c = DefaultSDConfig |
||||
type plain SDConfig |
||||
err := unmarshal((*plain)(c)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(c.Server) == 0 { |
||||
return errors.New("eureka_sd: empty or null eureka server") |
||||
} |
||||
url, err := url.Parse(c.Server) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(url.Scheme) == 0 || len(url.Host) == 0 { |
||||
return errors.New("eureka_sd: invalid eureka server URL") |
||||
} |
||||
return c.HTTPClientConfig.Validate() |
||||
} |
||||
|
||||
// Discovery provides service discovery based on a Eureka instance.
|
||||
type Discovery struct { |
||||
*refresh.Discovery |
||||
client *http.Client |
||||
server string |
||||
} |
||||
|
||||
// New creates a new Eureka discovery for the given role.
|
||||
func NewDiscovery(conf *SDConfig, logger log.Logger) (*Discovery, error) { |
||||
rt, err := config.NewRoundTripperFromConfig(conf.HTTPClientConfig, "eureka_sd", false, false) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
d := &Discovery{ |
||||
client: &http.Client{Transport: rt}, |
||||
server: conf.Server, |
||||
} |
||||
d.Discovery = refresh.NewDiscovery( |
||||
logger, |
||||
"eureka", |
||||
time.Duration(conf.RefreshInterval), |
||||
d.refresh, |
||||
) |
||||
return d, nil |
||||
} |
||||
|
||||
func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) { |
||||
apps, err := fetchApps(ctx, d.server, d.client) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
tg := &targetgroup.Group{ |
||||
Source: "eureka", |
||||
} |
||||
|
||||
for _, app := range apps.Applications { |
||||
targets := targetsForApp(&app) |
||||
tg.Targets = append(tg.Targets, targets...) |
||||
} |
||||
return []*targetgroup.Group{tg}, nil |
||||
} |
||||
|
||||
func targetsForApp(app *Application) []model.LabelSet { |
||||
targets := make([]model.LabelSet, 0, len(app.Instances)) |
||||
|
||||
// Gather info about the app's 'instances'. Each instance is considered a task.
|
||||
for _, t := range app.Instances { |
||||
var targetAddress string |
||||
if t.Port != nil { |
||||
targetAddress = net.JoinHostPort(t.HostName, strconv.Itoa(t.Port.Port)) |
||||
} else { |
||||
targetAddress = net.JoinHostPort(t.HostName, "80") |
||||
} |
||||
|
||||
target := model.LabelSet{ |
||||
model.AddressLabel: lv(targetAddress), |
||||
model.InstanceLabel: lv(t.InstanceID), |
||||
|
||||
appNameLabel: lv(app.Name), |
||||
appInstanceHostNameLabel: lv(t.HostName), |
||||
appInstanceHomePageURLLabel: lv(t.HomePageURL), |
||||
appInstanceStatusPageURLLabel: lv(t.StatusPageURL), |
||||
appInstanceHealthCheckURLLabel: lv(t.HealthCheckURL), |
||||
appInstanceIPAddrLabel: lv(t.IPAddr), |
||||
appInstanceVipAddressLabel: lv(t.VipAddress), |
||||
appInstanceSecureVipAddressLabel: lv(t.SecureVipAddress), |
||||
appInstanceStatusLabel: lv(t.Status), |
||||
appInstanceCountryIDLabel: lv(strconv.Itoa(t.CountryID)), |
||||
appInstanceIDLabel: lv(t.InstanceID), |
||||
} |
||||
|
||||
if t.Port != nil { |
||||
target[appInstancePortLabel] = lv(strconv.Itoa(t.Port.Port)) |
||||
target[appInstancePortEnabledLabel] = lv(strconv.FormatBool(t.Port.Enabled)) |
||||
} |
||||
|
||||
if t.SecurePort != nil { |
||||
target[appInstanceSecurePortLabel] = lv(strconv.Itoa(t.SecurePort.Port)) |
||||
target[appInstanceSecurePortEnabledLabel] = lv(strconv.FormatBool(t.SecurePort.Enabled)) |
||||
} |
||||
|
||||
if t.DataCenterInfo != nil { |
||||
target[appInstanceDataCenterInfoNameLabel] = lv(t.DataCenterInfo.Name) |
||||
|
||||
if t.DataCenterInfo.Metadata != nil { |
||||
for _, m := range t.DataCenterInfo.Metadata.Items { |
||||
ln := strutil.SanitizeLabelName(m.XMLName.Local) |
||||
target[model.LabelName(appInstanceDataCenterInfoMetadataPrefix+ln)] = lv(m.Content) |
||||
} |
||||
} |
||||
} |
||||
|
||||
if t.Metadata != nil { |
||||
for _, m := range t.Metadata.Items { |
||||
ln := strutil.SanitizeLabelName(m.XMLName.Local) |
||||
target[model.LabelName(appInstanceMetadataPrefix+ln)] = lv(m.Content) |
||||
} |
||||
} |
||||
|
||||
targets = append(targets, target) |
||||
|
||||
} |
||||
return targets |
||||
} |
||||
|
||||
func lv(s string) model.LabelValue { |
||||
return model.LabelValue(s) |
||||
} |
||||
@ -0,0 +1,246 @@ |
||||
// Copyright 2020 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 eureka |
||||
|
||||
import ( |
||||
"context" |
||||
"github.com/prometheus/prometheus/util/testutil" |
||||
"io" |
||||
"net/http" |
||||
"net/http/httptest" |
||||
"testing" |
||||
|
||||
"github.com/pkg/errors" |
||||
"github.com/prometheus/common/model" |
||||
"github.com/prometheus/prometheus/discovery/targetgroup" |
||||
) |
||||
|
||||
func testUpdateServices(respHandler http.HandlerFunc) ([]*targetgroup.Group, error) { |
||||
// Create a test server with mock HTTP handler.
|
||||
ts := httptest.NewServer(respHandler) |
||||
defer ts.Close() |
||||
|
||||
conf := SDConfig{ |
||||
Server: ts.URL, |
||||
} |
||||
|
||||
md, err := NewDiscovery(&conf, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return md.refresh(context.Background()) |
||||
} |
||||
|
||||
func TestEurekaSDHandleError(t *testing.T) { |
||||
var ( |
||||
errTesting = errors.Errorf("non 2xx status '%d' response during eureka service discovery", http.StatusInternalServerError) |
||||
respHandler = func(w http.ResponseWriter, r *http.Request) { |
||||
w.WriteHeader(http.StatusInternalServerError) |
||||
w.Header().Set("Content-Type", "application/xml") |
||||
io.WriteString(w, ``) |
||||
} |
||||
) |
||||
tgs, err := testUpdateServices(respHandler) |
||||
|
||||
testutil.ErrorEqual(t, err, errTesting) |
||||
testutil.Equals(t, len(tgs), 0) |
||||
} |
||||
|
||||
func TestEurekaSDEmptyList(t *testing.T) { |
||||
var ( |
||||
appsXML = `<applications> |
||||
<versions__delta>1</versions__delta> |
||||
<apps__hashcode/> |
||||
</applications>` |
||||
respHandler = func(w http.ResponseWriter, r *http.Request) { |
||||
w.WriteHeader(http.StatusOK) |
||||
w.Header().Set("Content-Type", "application/xml") |
||||
io.WriteString(w, appsXML) |
||||
} |
||||
) |
||||
tgs, err := testUpdateServices(respHandler) |
||||
testutil.Ok(t, err) |
||||
testutil.Equals(t, len(tgs), 1) |
||||
} |
||||
|
||||
func TestEurekaSDSendGroup(t *testing.T) { |
||||
var ( |
||||
appsXML = `<applications> |
||||
<versions__delta>1</versions__delta> |
||||
<apps__hashcode>UP_4_</apps__hashcode> |
||||
<application> |
||||
<name>CONFIG-SERVICE</name> |
||||
<instance> |
||||
<instanceId>config-service001.test.com:config-service:8080</instanceId> |
||||
<hostName>config-service001.test.com</hostName> |
||||
<app>CONFIG-SERVICE</app> |
||||
<ipAddr>192.133.83.31</ipAddr> |
||||
<status>UP</status> |
||||
<overriddenstatus>UNKNOWN</overriddenstatus> |
||||
<port enabled="true">8080</port> |
||||
<securePort enabled="false">8080</securePort> |
||||
<countryId>1</countryId> |
||||
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"> |
||||
<name>MyOwn</name> |
||||
</dataCenterInfo> |
||||
<leaseInfo> |
||||
<renewalIntervalInSecs>30</renewalIntervalInSecs> |
||||
<durationInSecs>90</durationInSecs> |
||||
<registrationTimestamp>1596003469304</registrationTimestamp> |
||||
<lastRenewalTimestamp>1596110179310</lastRenewalTimestamp> |
||||
<evictionTimestamp>0</evictionTimestamp> |
||||
<serviceUpTimestamp>1547190033103</serviceUpTimestamp> |
||||
</leaseInfo> |
||||
<metadata> |
||||
<instanceId>config-service001.test.com:config-service:8080</instanceId> |
||||
</metadata> |
||||
<homePageUrl>http://config-service001.test.com:8080/</homePageUrl>
|
||||
<statusPageUrl>http://config-service001.test.com:8080/info</statusPageUrl>
|
||||
<healthCheckUrl>http://config-service001.test.com 8080/health</healthCheckUrl>
|
||||
<vipAddress>config-service</vipAddress> |
||||
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer> |
||||
<lastUpdatedTimestamp>1596003469304</lastUpdatedTimestamp> |
||||
<lastDirtyTimestamp>1596003469304</lastDirtyTimestamp> |
||||
<actionType>ADDED</actionType> |
||||
</instance> |
||||
<instance> |
||||
<instanceId>config-service002.test.com:config-service:8080</instanceId> |
||||
<hostName>config-service002.test.com</hostName> |
||||
<app>CONFIG-SERVICE</app> |
||||
<ipAddr>192.133.83.31</ipAddr> |
||||
<status>UP</status> |
||||
<overriddenstatus>UNKNOWN</overriddenstatus> |
||||
<port enabled="true">8080</port> |
||||
<securePort enabled="false">8080</securePort> |
||||
<countryId>1</countryId> |
||||
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"> |
||||
<name>MyOwn</name> |
||||
</dataCenterInfo> |
||||
<leaseInfo> |
||||
<renewalIntervalInSecs>30</renewalIntervalInSecs> |
||||
<durationInSecs>90</durationInSecs> |
||||
<registrationTimestamp>1596003469304</registrationTimestamp> |
||||
<lastRenewalTimestamp>1596110179310</lastRenewalTimestamp> |
||||
<evictionTimestamp>0</evictionTimestamp> |
||||
<serviceUpTimestamp>1547190033103</serviceUpTimestamp> |
||||
</leaseInfo> |
||||
<metadata> |
||||
<instanceId>config-service002.test.com:config-service:8080</instanceId> |
||||
</metadata> |
||||
<homePageUrl>http://config-service002.test.com:8080/</homePageUrl>
|
||||
<statusPageUrl>http://config-service002.test.com:8080/info</statusPageUrl>
|
||||
<healthCheckUrl>http://config-service002.test.com:8080/health</healthCheckUrl>
|
||||
<vipAddress>config-service</vipAddress> |
||||
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer> |
||||
<lastUpdatedTimestamp>1596003469304</lastUpdatedTimestamp> |
||||
<lastDirtyTimestamp>1596003469304</lastDirtyTimestamp> |
||||
<actionType>ADDED</actionType> |
||||
</instance> |
||||
</application> |
||||
<application> |
||||
<name>META-SERVICE</name> |
||||
<instance> |
||||
<instanceId>meta-service002.test.com:meta-service:8080</instanceId> |
||||
<hostName>meta-service002.test.com</hostName> |
||||
<app>META-SERVICE</app> |
||||
<ipAddr>192.133.87.237</ipAddr> |
||||
<status>UP</status> |
||||
<overriddenstatus>UNKNOWN</overriddenstatus> |
||||
<port enabled="true">8080</port> |
||||
<securePort enabled="false">443</securePort> |
||||
<countryId>1</countryId> |
||||
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"> |
||||
<name>MyOwn</name> |
||||
</dataCenterInfo> |
||||
<leaseInfo> |
||||
<renewalIntervalInSecs>30</renewalIntervalInSecs> |
||||
<durationInSecs>90</durationInSecs> |
||||
<registrationTimestamp>1535444352472</registrationTimestamp> |
||||
<lastRenewalTimestamp>1596110168846</lastRenewalTimestamp> |
||||
<evictionTimestamp>0</evictionTimestamp> |
||||
<serviceUpTimestamp>1535444352472</serviceUpTimestamp> |
||||
</leaseInfo> |
||||
<metadata> |
||||
<project>meta-service</project> |
||||
<management.port>8090</management.port> |
||||
</metadata> |
||||
<homePageUrl>http://meta-service002.test.com:8080/</homePageUrl>
|
||||
<statusPageUrl>http://meta-service002.test.com:8080/info</statusPageUrl>
|
||||
<healthCheckUrl>http://meta-service002.test.com:8080/health</healthCheckUrl>
|
||||
<vipAddress>meta-service</vipAddress> |
||||
<secureVipAddress>meta-service</secureVipAddress> |
||||
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer> |
||||
<lastUpdatedTimestamp>1535444352472</lastUpdatedTimestamp> |
||||
<lastDirtyTimestamp>1535444352398</lastDirtyTimestamp> |
||||
<actionType>ADDED</actionType> |
||||
</instance> |
||||
<instance> |
||||
<instanceId>meta-service001.test.com:meta-service:8080</instanceId> |
||||
<hostName>meta-service001.test.com</hostName> |
||||
<app>META-SERVICE</app> |
||||
<ipAddr>192.133.87.236</ipAddr> |
||||
<status>UP</status> |
||||
<overriddenstatus>UNKNOWN</overriddenstatus> |
||||
<port enabled="true">8080</port> |
||||
<securePort enabled="false">443</securePort> |
||||
<countryId>1</countryId> |
||||
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"> |
||||
<name>MyOwn</name> |
||||
</dataCenterInfo> |
||||
<leaseInfo> |
||||
<renewalIntervalInSecs>30</renewalIntervalInSecs> |
||||
<durationInSecs>90</durationInSecs> |
||||
<registrationTimestamp>1535444352472</registrationTimestamp> |
||||
<lastRenewalTimestamp>1596110168846</lastRenewalTimestamp> |
||||
<evictionTimestamp>0</evictionTimestamp> |
||||
<serviceUpTimestamp>1535444352472</serviceUpTimestamp> |
||||
</leaseInfo> |
||||
<metadata> |
||||
<project>meta-service</project> |
||||
<management.port>8090</management.port> |
||||
</metadata> |
||||
<homePageUrl>http://meta-service001.test.com:8080/</homePageUrl>
|
||||
<statusPageUrl>http://meta-service001.test.com:8080/info</statusPageUrl>
|
||||
<healthCheckUrl>http://meta-service001.test.com:8080/health</healthCheckUrl>
|
||||
<vipAddress>meta-service</vipAddress> |
||||
<secureVipAddress>meta-service</secureVipAddress> |
||||
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer> |
||||
<lastUpdatedTimestamp>1535444352472</lastUpdatedTimestamp> |
||||
<lastDirtyTimestamp>1535444352398</lastDirtyTimestamp> |
||||
<actionType>ADDED</actionType> |
||||
</instance> |
||||
</application> |
||||
</applications>` |
||||
respHandler = func(w http.ResponseWriter, r *http.Request) { |
||||
w.WriteHeader(http.StatusOK) |
||||
w.Header().Set("Content-Type", "application/xml") |
||||
io.WriteString(w, appsXML) |
||||
} |
||||
) |
||||
|
||||
tgs, err := testUpdateServices(respHandler) |
||||
testutil.Ok(t, err) |
||||
testutil.Equals(t, len(tgs), 1) |
||||
|
||||
tg := tgs[0] |
||||
testutil.Equals(t, tg.Source, "eureka") |
||||
testutil.Equals(t, len(tg.Targets), 4) |
||||
|
||||
tgt := tg.Targets[0] |
||||
testutil.Equals(t, tgt[model.AddressLabel], model.LabelValue("config-service001.test.com:8080")) |
||||
|
||||
tgt = tg.Targets[2] |
||||
testutil.Equals(t, tgt[model.AddressLabel], model.LabelValue("meta-service002.test.com:8080")) |
||||
} |
||||
@ -0,0 +1,66 @@ |
||||
# A example scrape configuration for running Prometheus with Eureka. |
||||
|
||||
scrape_configs: |
||||
|
||||
# Make Prometheus scrape itself for metrics. |
||||
- job_name: 'prometheus' |
||||
static_configs: |
||||
- targets: ['localhost:9090'] |
||||
|
||||
# Discover Eureka services to scrape. |
||||
- job_name: 'eureka' |
||||
|
||||
# Scrape Eureka itself to discover new services. |
||||
eureka_sd_configs: |
||||
- server: http://localhost:8761/eureka |
||||
|
||||
relabel_configs: |
||||
# You can use Eureka's application instance metadata. |
||||
# If you are using SpringBoot, you can add metadata using eureka.instance.metadataMap like this: |
||||
# application.yaml (spring-boot) |
||||
# eureka: |
||||
# instance: |
||||
# metadataMap: |
||||
# "prometheus.scrape": "true" |
||||
# "prometheus.path": "/actuator/prometheus" |
||||
# "prometheus.port": "8080" |
||||
# |
||||
# |
||||
# Example relabel to scrape only application that have |
||||
# "prometheus.scrape = true" metadata. |
||||
# - source_labels: [__meta_eureka_app_instance_metadata_prometheus_scrape] |
||||
# action: keep |
||||
# regex: true |
||||
# |
||||
# application.yaml (spring-boot) |
||||
# eureka: |
||||
# instance: |
||||
# metadataMap: |
||||
# "prometheus.scrape": "true" |
||||
# |
||||
# Example relabel to customize metric path based on application |
||||
# "prometheus.path = <metric path>" annotation. |
||||
# - source_labels: [__meta_eureka_app_instance_metadata_prometheus_path] |
||||
# action: replace |
||||
# target_label: __metrics_path__ |
||||
# regex: (.+) |
||||
# |
||||
# application.yaml (spring-boot) |
||||
# eureka: |
||||
# instance: |
||||
# metadataMap: |
||||
# "prometheus.path": "/actuator/prometheus" |
||||
# |
||||
# Example relabel to scrape only single, desired port for the application |
||||
# based on application "prometheus.port = <port>" metadata. |
||||
# - source_labels: [__address__, __meta_eureka_app_instance_metadata_prometheus_port] |
||||
# action: replace |
||||
# regex: ([^:]+)(?::\d+)?;(\d+) |
||||
# replacement: $1:$2 |
||||
# target_label: __address__ |
||||
# |
||||
# application.yaml (spring-boot) |
||||
# eureka: |
||||
# instance: |
||||
# metadataMap: |
||||
# "prometheus.port": "8080" |
||||
Loading…
Reference in new issue