File: metrics.go

package info (click to toggle)
incus 6.0.5-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 24,428 kB
  • sloc: sh: 16,313; ansic: 3,121; python: 457; makefile: 337; ruby: 51; sql: 50; lisp: 6
file content (111 lines) | stat: -rw-r--r-- 2,726 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package endpoints

import (
	"fmt"
	"net"
	"strings"
	"time"

	"github.com/lxc/incus/v6/internal/ports"
	"github.com/lxc/incus/v6/internal/server/endpoints/listeners"
	internalUtil "github.com/lxc/incus/v6/internal/util"
	"github.com/lxc/incus/v6/shared/logger"
	localtls "github.com/lxc/incus/v6/shared/tls"
)

func metricsCreateListener(address string, cert *localtls.CertInfo) (net.Listener, error) {
	// Listening on `tcp` network with address 0.0.0.0 will end up with listening
	// on both IPv4 and IPv6 interfaces. Pass `tcp4` to make it
	// work only on 0.0.0.0. https://go-review.googlesource.com/c/go/+/45771/
	listenAddress := internalUtil.CanonicalNetworkAddress(address, ports.HTTPSMetricsDefaultPort)
	protocol := "tcp"

	if strings.HasPrefix(listenAddress, "0.0.0.0") {
		protocol = "tcp4"
	}

	listener, err := net.Listen(protocol, listenAddress)
	if err != nil {
		return nil, fmt.Errorf("Bind network address: %w", err)
	}

	return listeners.NewFancyTLSListener(listener, cert), nil
}

// MetricsAddress returns the network address of the metrics endpoint, or an
// empty string if there's no metrics endpoint.
func (e *Endpoints) MetricsAddress() string {
	e.mu.RLock()
	defer e.mu.RUnlock()

	listener := e.listeners[metrics]
	if listener == nil {
		return ""
	}

	return listener.Addr().String()
}

// MetricsUpdateAddress updates the address for the metrics endpoint, shutting it down and restarting it.
func (e *Endpoints) MetricsUpdateAddress(address string, cert *localtls.CertInfo) error {
	if address != "" {
		address = internalUtil.CanonicalNetworkAddress(address, ports.HTTPSMetricsDefaultPort)
	}

	oldAddress := e.MetricsAddress()
	if address == oldAddress {
		return nil
	}

	logger.Infof("Update metrics address")

	e.mu.Lock()
	defer e.mu.Unlock()

	// Close the previous socket
	_ = e.closeListener(metrics)

	// If turning off listening, we're done
	if address == "" {
		return nil
	}

	// Attempt to setup the new listening socket
	getListener := func(address string) (*net.Listener, error) {
		var err error
		var listener net.Listener

		for range 10 { // Ten retries over a second seems reasonable.
			listener, err = metricsCreateListener(address, cert)
			if err == nil {
				break
			}

			time.Sleep(100 * time.Millisecond)
		}

		if err != nil {
			return nil, fmt.Errorf("Cannot listen on http socket: %w", err)
		}

		return &listener, nil
	}

	// Set up the listener
	listener, err := getListener(address)
	if err != nil {
		// Attempt to revert to the previous address
		listener, err1 := getListener(oldAddress)
		if err1 == nil {
			e.listeners[metrics] = *listener
			e.serve(metrics)
		}

		return err
	}

	e.listeners[metrics] = *listener
	e.serve(metrics)

	return nil
}