From d02591814bcd4bd8fbde8008c61d81e0735fa9e8 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Thu, 5 May 2016 11:45:51 +0200 Subject: [PATCH 1/2] discovery/dns: move dns to own package --- retrieval/discovery/dns.go | 217 +----------------------------- retrieval/discovery/dns/dns.go | 234 +++++++++++++++++++++++++++++++++ retrieval/targetmanager.go | 2 +- 3 files changed, 240 insertions(+), 213 deletions(-) create mode 100644 retrieval/discovery/dns/dns.go diff --git a/retrieval/discovery/dns.go b/retrieval/discovery/dns.go index cd1c01bf6..c413efdfc 100644 --- a/retrieval/discovery/dns.go +++ b/retrieval/discovery/dns.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Prometheus Authors +// 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 @@ -14,219 +14,12 @@ package discovery import ( - "fmt" - "net" - "strings" - "sync" - "time" - - "github.com/miekg/dns" - "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/retrieval/discovery/dns" "github.com/prometheus/prometheus/config" ) -const ( - resolvConf = "/etc/resolv.conf" - - dnsNameLabel = model.MetaLabelPrefix + "dns_name" - - // Constants for instrumentation. - namespace = "prometheus" -) - -var ( - dnsSDLookupsCount = prometheus.NewCounter( - prometheus.CounterOpts{ - Namespace: namespace, - Name: "dns_sd_lookups_total", - Help: "The number of DNS-SD lookups.", - }) - dnsSDLookupFailuresCount = prometheus.NewCounter( - prometheus.CounterOpts{ - Namespace: namespace, - Name: "dns_sd_lookup_failures_total", - Help: "The number of DNS-SD lookup failures.", - }) -) - -func init() { - prometheus.MustRegister(dnsSDLookupFailuresCount) - prometheus.MustRegister(dnsSDLookupsCount) -} - -// DNSDiscovery periodically performs DNS-SD requests. It implements -// the TargetProvider interface. -type DNSDiscovery struct { - names []string - - done chan struct{} - interval time.Duration - m sync.RWMutex - port int - qtype uint16 -} - -// NewDNSDiscovery returns a new DNSDiscovery which periodically refreshes its targets. -func NewDNSDiscovery(conf *config.DNSSDConfig) *DNSDiscovery { - qtype := dns.TypeSRV - switch strings.ToUpper(conf.Type) { - case "A": - qtype = dns.TypeA - case "AAAA": - qtype = dns.TypeAAAA - case "SRV": - qtype = dns.TypeSRV - } - return &DNSDiscovery{ - names: conf.Names, - done: make(chan struct{}), - interval: time.Duration(conf.RefreshInterval), - qtype: qtype, - port: conf.Port, - } -} - -// Run implements the TargetProvider interface. -func (dd *DNSDiscovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) { - defer close(ch) - - ticker := time.NewTicker(dd.interval) - defer ticker.Stop() - - // Get an initial set right away. - dd.refreshAll(ch) - - for { - select { - case <-ticker.C: - dd.refreshAll(ch) - case <-ctx.Done(): - return - } - } -} - -func (dd *DNSDiscovery) refreshAll(ch chan<- []*config.TargetGroup) { - var wg sync.WaitGroup - - wg.Add(len(dd.names)) - for _, name := range dd.names { - go func(n string) { - if err := dd.refresh(n, ch); err != nil { - log.Errorf("Error refreshing DNS targets: %s", err) - } - wg.Done() - }(name) - } - - wg.Wait() -} - -func (dd *DNSDiscovery) refresh(name string, ch chan<- []*config.TargetGroup) error { - response, err := lookupAll(name, dd.qtype) - dnsSDLookupsCount.Inc() - if err != nil { - dnsSDLookupFailuresCount.Inc() - return err - } - - tg := &config.TargetGroup{} - - for _, record := range response.Answer { - target := model.LabelValue("") - switch addr := record.(type) { - case *dns.SRV: - // Remove the final dot from rooted DNS names to make them look more usual. - addr.Target = strings.TrimRight(addr.Target, ".") - - target = model.LabelValue(fmt.Sprintf("%s:%d", addr.Target, addr.Port)) - case *dns.A: - target = model.LabelValue(fmt.Sprintf("%s:%d", addr.A, dd.port)) - case *dns.AAAA: - target = model.LabelValue(fmt.Sprintf("%s:%d", addr.AAAA, dd.port)) - default: - log.Warnf("%q is not a valid SRV record", record) - continue - - } - tg.Targets = append(tg.Targets, model.LabelSet{ - model.AddressLabel: target, - dnsNameLabel: model.LabelValue(name), - }) - } - - tg.Source = name - ch <- []*config.TargetGroup{tg} - - return nil -} - -func lookupAll(name string, qtype uint16) (*dns.Msg, error) { - conf, err := dns.ClientConfigFromFile(resolvConf) - if err != nil { - return nil, fmt.Errorf("could not load resolv.conf: %s", err) - } - - client := &dns.Client{} - response := &dns.Msg{} - - for _, server := range conf.Servers { - servAddr := net.JoinHostPort(server, conf.Port) - for _, suffix := range conf.Search { - response, err = lookup(name, qtype, client, servAddr, suffix, false) - if err != nil { - log.Warnf("resolving %s.%s failed: %s", name, suffix, err) - continue - } - if len(response.Answer) > 0 { - return response, nil - } - } - response, err = lookup(name, qtype, client, servAddr, "", false) - if err == nil { - return response, nil - } - } - return response, fmt.Errorf("could not resolve %s: No server responded", name) -} - -func lookup(name string, queryType uint16, client *dns.Client, servAddr string, suffix string, edns bool) (*dns.Msg, error) { - msg := &dns.Msg{} - lname := strings.Join([]string{name, suffix}, ".") - msg.SetQuestion(dns.Fqdn(lname), queryType) - - if edns { - opt := &dns.OPT{ - Hdr: dns.RR_Header{ - Name: ".", - Rrtype: dns.TypeOPT, - }, - } - opt.SetUDPSize(dns.DefaultMsgSize) - msg.Extra = append(msg.Extra, opt) - } - - response, _, err := client.Exchange(msg, servAddr) - if err != nil { - return nil, err - } - if msg.Id != response.Id { - return nil, fmt.Errorf("DNS ID mismatch, request: %d, response: %d", msg.Id, response.Id) - } - - if response.MsgHdr.Truncated { - if client.Net == "tcp" { - return nil, fmt.Errorf("got truncated message on tcp") - } - if edns { // Truncated even though EDNS is used - client.Net = "tcp" - } - return lookup(name, queryType, client, servAddr, suffix, !edns) - } - - return response, nil +// NewDNS creates a new DNS based discovery. +func NewDNS(conf *config.DNSSDConfig) *dns.Discovery { + return dns.NewDiscovery(conf) } diff --git a/retrieval/discovery/dns/dns.go b/retrieval/discovery/dns/dns.go new file mode 100644 index 000000000..8b3f19ca5 --- /dev/null +++ b/retrieval/discovery/dns/dns.go @@ -0,0 +1,234 @@ +// 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 dns + +import ( + "fmt" + "net" + "strings" + "sync" + "time" + + "github.com/miekg/dns" + "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" +) + +const ( + resolvConf = "/etc/resolv.conf" + + dnsNameLabel = model.MetaLabelPrefix + "dns_name" + + // Constants for instrumentation. + namespace = "prometheus" +) + +var ( + dnsSDLookupsCount = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: namespace, + Name: "dns_sd_lookups_total", + Help: "The number of DNS-SD lookups.", + }) + dnsSDLookupFailuresCount = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: namespace, + Name: "dns_sd_lookup_failures_total", + Help: "The number of DNS-SD lookup failures.", + }) +) + +func init() { + prometheus.MustRegister(dnsSDLookupFailuresCount) + prometheus.MustRegister(dnsSDLookupsCount) +} + +// Discovery periodically performs DNS-SD requests. It implements +// the TargetProvider interface. +type Discovery struct { + names []string + + interval time.Duration + m sync.RWMutex + port int + qtype uint16 +} + +// NewDiscovery returns a new Discovery which periodically refreshes its targets. +func NewDiscovery(conf *config.DNSSDConfig) *Discovery { + qtype := dns.TypeSRV + switch strings.ToUpper(conf.Type) { + case "A": + qtype = dns.TypeA + case "AAAA": + qtype = dns.TypeAAAA + case "SRV": + qtype = dns.TypeSRV + } + return &Discovery{ + names: conf.Names, + interval: time.Duration(conf.RefreshInterval), + qtype: qtype, + port: conf.Port, + } +} + +// Run implements the TargetProvider interface. +func (dd *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) { + defer close(ch) + + ticker := time.NewTicker(dd.interval) + defer ticker.Stop() + + // Get an initial set right away. + dd.refreshAll(ctx, ch) + + for { + select { + case <-ticker.C: + dd.refreshAll(ctx, ch) + case <-ctx.Done(): + return + } + } +} + +func (dd *Discovery) refreshAll(ctx context.Context, ch chan<- []*config.TargetGroup) { + var wg sync.WaitGroup + + wg.Add(len(dd.names)) + for _, name := range dd.names { + go func(n string) { + if err := dd.refresh(n, ctx, ch); err != nil { + log.Errorf("Error refreshing DNS targets: %s", err) + } + wg.Done() + }(name) + } + + wg.Wait() +} + +func (dd *Discovery) refresh(name string, ctx context.Context, ch chan<- []*config.TargetGroup) error { + response, err := lookupAll(name, dd.qtype) + dnsSDLookupsCount.Inc() + if err != nil { + dnsSDLookupFailuresCount.Inc() + return err + } + + tg := &config.TargetGroup{} + + for _, record := range response.Answer { + target := model.LabelValue("") + switch addr := record.(type) { + case *dns.SRV: + // Remove the final dot from rooted DNS names to make them look more usual. + addr.Target = strings.TrimRight(addr.Target, ".") + + target = model.LabelValue(fmt.Sprintf("%s:%d", addr.Target, addr.Port)) + case *dns.A: + target = model.LabelValue(fmt.Sprintf("%s:%d", addr.A, dd.port)) + case *dns.AAAA: + target = model.LabelValue(fmt.Sprintf("%s:%d", addr.AAAA, dd.port)) + default: + log.Warnf("%q is not a valid SRV record", record) + continue + + } + tg.Targets = append(tg.Targets, model.LabelSet{ + model.AddressLabel: target, + dnsNameLabel: model.LabelValue(name), + }) + } + + tg.Source = name + select { + case <-ctx.Done(): + return ctx.Err() + case ch <- []*config.TargetGroup{tg}: + } + + return nil +} + +func lookupAll(name string, qtype uint16) (*dns.Msg, error) { + conf, err := dns.ClientConfigFromFile(resolvConf) + if err != nil { + return nil, fmt.Errorf("could not load resolv.conf: %s", err) + } + + client := &dns.Client{} + response := &dns.Msg{} + + for _, server := range conf.Servers { + servAddr := net.JoinHostPort(server, conf.Port) + for _, suffix := range conf.Search { + response, err = lookup(name, qtype, client, servAddr, suffix, false) + if err != nil { + log.Warnf("resolving %s.%s failed: %s", name, suffix, err) + continue + } + if len(response.Answer) > 0 { + return response, nil + } + } + response, err = lookup(name, qtype, client, servAddr, "", false) + if err == nil { + return response, nil + } + } + return response, fmt.Errorf("could not resolve %s: No server responded", name) +} + +func lookup(name string, queryType uint16, client *dns.Client, servAddr string, suffix string, edns bool) (*dns.Msg, error) { + msg := &dns.Msg{} + lname := strings.Join([]string{name, suffix}, ".") + msg.SetQuestion(dns.Fqdn(lname), queryType) + + if edns { + opt := &dns.OPT{ + Hdr: dns.RR_Header{ + Name: ".", + Rrtype: dns.TypeOPT, + }, + } + opt.SetUDPSize(dns.DefaultMsgSize) + msg.Extra = append(msg.Extra, opt) + } + + response, _, err := client.Exchange(msg, servAddr) + if err != nil { + return nil, err + } + if msg.Id != response.Id { + return nil, fmt.Errorf("DNS ID mismatch, request: %d, response: %d", msg.Id, response.Id) + } + + if response.MsgHdr.Truncated { + if client.Net == "tcp" { + return nil, fmt.Errorf("got truncated message on tcp") + } + if edns { // Truncated even though EDNS is used + client.Net = "tcp" + } + return lookup(name, queryType, client, servAddr, suffix, !edns) + } + + return response, nil +} diff --git a/retrieval/targetmanager.go b/retrieval/targetmanager.go index 4d0e7af74..dc02cae97 100644 --- a/retrieval/targetmanager.go +++ b/retrieval/targetmanager.go @@ -360,7 +360,7 @@ func providersFromConfig(cfg *config.ScrapeConfig) map[string]TargetProvider { } for i, c := range cfg.DNSSDConfigs { - app("dns", i, discovery.NewDNSDiscovery(c)) + app("dns", i, discovery.NewDNS(c)) } for i, c := range cfg.FileSDConfigs { app("file", i, discovery.NewFileDiscovery(c)) From d959d2b90a6f1e24760ab1d47bbf9bf2fcd2933e Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Fri, 6 May 2016 10:46:22 +0200 Subject: [PATCH 2/2] discovery/dns: Maintain argument order consistency --- retrieval/discovery/dns/dns.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/retrieval/discovery/dns/dns.go b/retrieval/discovery/dns/dns.go index 8b3f19ca5..432f1c042 100644 --- a/retrieval/discovery/dns/dns.go +++ b/retrieval/discovery/dns/dns.go @@ -114,7 +114,7 @@ func (dd *Discovery) refreshAll(ctx context.Context, ch chan<- []*config.TargetG wg.Add(len(dd.names)) for _, name := range dd.names { go func(n string) { - if err := dd.refresh(n, ctx, ch); err != nil { + if err := dd.refresh(ctx, n, ch); err != nil { log.Errorf("Error refreshing DNS targets: %s", err) } wg.Done() @@ -124,7 +124,7 @@ func (dd *Discovery) refreshAll(ctx context.Context, ch chan<- []*config.TargetG wg.Wait() } -func (dd *Discovery) refresh(name string, ctx context.Context, ch chan<- []*config.TargetGroup) error { +func (dd *Discovery) refresh(ctx context.Context, name string, ch chan<- []*config.TargetGroup) error { response, err := lookupAll(name, dd.qtype) dnsSDLookupsCount.Inc() if err != nil {