11package cvo
22
33import (
4+ "context"
5+ "crypto/tls"
6+ "net"
7+ "net/http"
48 "time"
59
10+ "github.com/cockroachdb/cmux"
611 "github.com/prometheus/client_golang/prometheus"
12+ "github.com/prometheus/client_golang/prometheus/promhttp"
713 corev1 "k8s.io/api/core/v1"
814 apierrors "k8s.io/apimachinery/pkg/api/errors"
915 "k8s.io/apimachinery/pkg/labels"
@@ -16,7 +22,9 @@ import (
1622 "github.com/openshift/cluster-version-operator/pkg/internal"
1723)
1824
19- func (optr * Operator ) registerMetrics (coInformer cache.SharedInformer ) error {
25+ // RegisterMetrics initializes metrics and registers them with the
26+ // Prometheus implementation.
27+ func (optr * Operator ) RegisterMetrics (coInformer cache.SharedInformer ) error {
2028 m := newOperatorMetrics (optr )
2129 coInformer .AddEventHandler (cache.ResourceEventHandlerFuncs {
2230 UpdateFunc : m .clusterOperatorChanged ,
@@ -92,6 +100,88 @@ version for 'cluster', or empty for 'initial'.
92100 }
93101}
94102
103+ // RunMetrics launches an server bound to listenAddress serving
104+ // Prometheus metrics at /metrics over HTTP, and, if tlsConfig is
105+ // non-nil, also over HTTPS. Continues serving until runContext.Done()
106+ // and then attempts a clean shutdown limited by shutdownContext.Done().
107+ // Assumes runContext.Done() occurs before or simultaneously with
108+ // shutdownContext.Done().
109+ func RunMetrics (runContext context.Context , shutdownContext context.Context , listenAddress string , tlsConfig * tls.Config ) error {
110+ handler := http .NewServeMux ()
111+ handler .Handle ("/metrics" , promhttp .Handler ())
112+ server := & http.Server {
113+ Handler : handler ,
114+ }
115+
116+ tcpListener , err := net .Listen ("tcp" , listenAddress )
117+ if err != nil {
118+ return err
119+ }
120+
121+ // if a TLS connection was requested, set up a connection mux that will send TLS requests to
122+ // the TLS server but send HTTP requests to the HTTP server. Preserves the ability for legacy
123+ // HTTP, needed during upgrade, while still allowing TLS certs and end to end metrics protection.
124+ mux := cmux .New (tcpListener )
125+
126+ errorChannel := make (chan error , 1 )
127+ errorChannelCount := 1
128+
129+ go func () {
130+ // match HTTP first
131+ httpListener := mux .Match (cmux .HTTP1 ())
132+ klog .Infof ("Metrics port listening for HTTP on %v" , listenAddress )
133+ errorChannel <- server .Serve (httpListener )
134+ }()
135+
136+ if tlsConfig != nil {
137+ errorChannelCount ++
138+ go func () {
139+ tlsListener := tls .NewListener (mux .Match (cmux .Any ()), tlsConfig )
140+ klog .Infof ("Metrics port listening for HTTPS on %v" , listenAddress )
141+ errorChannel <- server .Serve (tlsListener )
142+ }()
143+ }
144+
145+ errorChannelCount ++
146+ go func () {
147+ errorChannel <- mux .Serve ()
148+ }()
149+
150+ shutdown := false
151+ var loopError error
152+ for errorChannelCount > 0 {
153+ if shutdown {
154+ err := <- errorChannel
155+ errorChannelCount --
156+ if err != nil && err != http .ErrServerClosed && err != cmux .ErrListenerClosed {
157+ if loopError == nil {
158+ loopError = err
159+ } else if err != nil { // log the error we are discarding
160+ klog .Errorf ("Failed to gracefully shut down metrics server: %s" , err )
161+ }
162+ }
163+ } else {
164+ select {
165+ case <- runContext .Done (): // clean shutdown
166+ case err := <- errorChannel : // crashed before a shutdown was requested
167+ errorChannelCount --
168+ if err != nil && err != http .ErrServerClosed && err != cmux .ErrListenerClosed {
169+ loopError = err
170+ }
171+ }
172+ shutdown = true
173+ shutdownError := server .Shutdown (shutdownContext )
174+ if loopError == nil {
175+ loopError = shutdownError
176+ } else if shutdownError != nil { // log the error we are discarding
177+ klog .Errorf ("Failed to gracefully shut down metrics server: %s" , shutdownError )
178+ }
179+ }
180+ }
181+
182+ return loopError
183+ }
184+
95185type conditionKey struct {
96186 Name string
97187 Type string
0 commit comments