mirror of
https://github.com/coredns/coredns.git
synced 2026-10-08 19:45:21 -04:00
fix(metrics): release listener on TLS startup failure (#8527)
This commit is contained in:
@@ -40,6 +40,7 @@ type Metrics struct {
|
|||||||
plugins map[string]struct{} // all available plugins, used to determine which plugin made the client write
|
plugins map[string]struct{} // all available plugins, used to determine which plugin made the client write
|
||||||
|
|
||||||
tlsConfigPath string
|
tlsConfigPath string
|
||||||
|
serveTLS func(net.Listener, *http.Server, *web.FlagConfig, *slog.Logger) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new instance of Metrics with the given address.
|
// New returns a new instance of Metrics with the given address.
|
||||||
@@ -49,6 +50,7 @@ func New(addr string) *Metrics {
|
|||||||
Reg: prometheus.DefaultRegisterer.(*prometheus.Registry),
|
Reg: prometheus.DefaultRegisterer.(*prometheus.Registry),
|
||||||
zoneMap: make(map[string]struct{}),
|
zoneMap: make(map[string]struct{}),
|
||||||
plugins: pluginList(caddy.ListPlugins()),
|
plugins: pluginList(caddy.ListPlugins()),
|
||||||
|
serveTLS: web.Serve,
|
||||||
}
|
}
|
||||||
|
|
||||||
return met
|
return met
|
||||||
@@ -117,6 +119,13 @@ func (sl *startupListener) Ready() <-chan struct{} {
|
|||||||
|
|
||||||
// OnStartup sets up the metrics on startup.
|
// OnStartup sets up the metrics on startup.
|
||||||
func (m *Metrics) OnStartup() error {
|
func (m *Metrics) OnStartup() error {
|
||||||
|
if m.tlsConfigPath != "" {
|
||||||
|
if err := web.Validate(m.tlsConfigPath); err != nil {
|
||||||
|
log.Errorf("Invalid TLS config: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ln, err := reuseport.Listen("tcp", m.Addr)
|
ln, err := reuseport.Listen("tcp", m.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to start metrics handler: %s", err)
|
log.Errorf("Failed to start metrics handler: %s", err)
|
||||||
@@ -151,12 +160,6 @@ func (m *Metrics) OnStartup() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check TLS config file existence
|
|
||||||
if _, err := os.Stat(m.tlsConfigPath); os.IsNotExist(err) {
|
|
||||||
log.Errorf("TLS config file does not exist: %s", m.tlsConfigPath)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create web config for ListenAndServe
|
// Create web config for ListenAndServe
|
||||||
webConfig := &web.FlagConfig{
|
webConfig := &web.FlagConfig{
|
||||||
WebListenAddresses: &[]string{m.Addr},
|
WebListenAddresses: &[]string{m.Addr},
|
||||||
@@ -173,7 +176,7 @@ func (m *Metrics) OnStartup() error {
|
|||||||
// web.Serve() never returns nil, it always returns a non-nil error and
|
// web.Serve() never returns nil, it always returns a non-nil error and
|
||||||
// it doesn't retun anything if server starts successfully.
|
// it doesn't retun anything if server starts successfully.
|
||||||
// startupListener handles capturing succesful startup.
|
// startupListener handles capturing succesful startup.
|
||||||
err := web.Serve(m.ln, server, webConfig, logger)
|
err := m.serveTLS(m.ln, server, webConfig, logger)
|
||||||
if err != nil && err != http.ErrServerClosed {
|
if err != nil && err != http.ErrServerClosed {
|
||||||
log.Errorf("Failed to start HTTPS metrics server: %v", err)
|
log.Errorf("Failed to start HTTPS metrics server: %v", err)
|
||||||
startUpErr <- err
|
startUpErr <- err
|
||||||
@@ -183,6 +186,10 @@ func (m *Metrics) OnStartup() error {
|
|||||||
// Wait for startup errors
|
// Wait for startup errors
|
||||||
select {
|
select {
|
||||||
case err := <-startUpErr:
|
case err := <-startUpErr:
|
||||||
|
if closeErr := ln.Close(); closeErr != nil {
|
||||||
|
log.Errorf("Failed to close metrics listener after startup error: %s", closeErr)
|
||||||
|
}
|
||||||
|
m.lnSetup = false
|
||||||
return err
|
return err
|
||||||
case <-startupListener.Ready():
|
case <-startupListener.Ready():
|
||||||
log.Infof("Server is ready and accepting connections")
|
log.Infof("Server is ready and accepting connections")
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -24,6 +26,7 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
"github.com/prometheus/exporter-toolkit/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -408,6 +411,63 @@ func TestMetricsTLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMetricsTLSValidationErrorDoesNotClaimAddress(t *testing.T) {
|
||||||
|
probe, err := net.Listen("tcp", "localhost:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to reserve test address: %v", err)
|
||||||
|
}
|
||||||
|
addr := probe.Addr().String()
|
||||||
|
if err := probe.Close(); err != nil {
|
||||||
|
t.Fatalf("failed to release test address: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
met := New(addr)
|
||||||
|
met.tlsConfigPath = "test_data/configs/junk.yml"
|
||||||
|
if err := met.OnStartup(); err == nil {
|
||||||
|
t.Fatal("expected invalid TLS config to fail startup")
|
||||||
|
}
|
||||||
|
if met.lnSetup {
|
||||||
|
t.Fatal("listener marked as set up after failed startup")
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed startup kept metrics address %s: %v", addr, err)
|
||||||
|
}
|
||||||
|
listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetricsTLSServeErrorReleasesAddress(t *testing.T) {
|
||||||
|
probe, err := net.Listen("tcp", "localhost:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to reserve test address: %v", err)
|
||||||
|
}
|
||||||
|
addr := probe.Addr().String()
|
||||||
|
if err := probe.Close(); err != nil {
|
||||||
|
t.Fatalf("failed to release test address: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serveErr := errors.New("injected TLS serve failure")
|
||||||
|
met := New(addr)
|
||||||
|
met.tlsConfigPath = "test_data/configs/empty.yml"
|
||||||
|
met.serveTLS = func(net.Listener, *http.Server, *web.FlagConfig, *slog.Logger) error {
|
||||||
|
return serveErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := met.OnStartup(); !errors.Is(err, serveErr) {
|
||||||
|
t.Fatalf("expected injected TLS serve error, got %v", err)
|
||||||
|
}
|
||||||
|
if met.lnSetup {
|
||||||
|
t.Fatal("listener marked as set up after TLS serve failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TLS serve failure kept metrics address %s: %v", addr, err)
|
||||||
|
}
|
||||||
|
listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func TestMetrics(t *testing.T) {
|
func TestMetrics(t *testing.T) {
|
||||||
met := New("localhost:0")
|
met := New("localhost:0")
|
||||||
if err := met.OnStartup(); err != nil {
|
if err := met.OnStartup(); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user