From c2310e1279423c6f26dd763e889b077c3c24fddb Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Wed, 8 Nov 2023 08:49:58 -0600 Subject: [PATCH] Implement authz config file reloading Kubernetes-commit: 5dc92ada068cb80a2866cfaa1f9aa760d2524680 --- pkg/server/config.go | 5 + .../authorizationconfig/metrics/metrics.go | 101 ++++++++++++++++ .../metrics/metrics_test.go | 109 ++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 pkg/server/options/authorizationconfig/metrics/metrics.go create mode 100644 pkg/server/options/authorizationconfig/metrics/metrics_test.go diff --git a/pkg/server/config.go b/pkg/server/config.go index d42baab63..a48bee2c9 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -668,6 +668,11 @@ func (c *Config) DrainedNotify() <-chan struct{} { return c.lifecycleSignals.InFlightRequestsDrained.Signaled() } +// ShutdownInitiated returns a lifecycle signal of apiserver shutdown having been initiated. +func (c *Config) ShutdownInitiatedNotify() <-chan struct{} { + return c.lifecycleSignals.ShutdownInitiated.Signaled() +} + // Complete fills in any fields not set that are required to have valid data and can be derived // from other fields. If you're going to `ApplyOptions`, do that first. It's mutating the receiver. func (c *Config) Complete(informers informers.SharedInformerFactory) CompletedConfig { diff --git a/pkg/server/options/authorizationconfig/metrics/metrics.go b/pkg/server/options/authorizationconfig/metrics/metrics.go new file mode 100644 index 000000000..09089348a --- /dev/null +++ b/pkg/server/options/authorizationconfig/metrics/metrics.go @@ -0,0 +1,101 @@ +/* +Copyright 2024 The Kubernetes 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 metrics + +import ( + "crypto/sha256" + "fmt" + "hash" + "sync" + + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/legacyregistry" +) + +const ( + namespace = "apiserver" + subsystem = "authorization_config_controller" +) + +var ( + authorizationConfigAutomaticReloadsTotal = metrics.NewCounterVec( + &metrics.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "automatic_reloads_total", + Help: "Total number of automatic reloads of authorization configuration split by status and apiserver identity.", + StabilityLevel: metrics.ALPHA, + }, + []string{"status", "apiserver_id_hash"}, + ) + + authorizationConfigAutomaticReloadLastTimestampSeconds = metrics.NewGaugeVec( + &metrics.GaugeOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "automatic_reload_last_timestamp_seconds", + Help: "Timestamp of the last automatic reload of authorization configuration split by status and apiserver identity.", + StabilityLevel: metrics.ALPHA, + }, + []string{"status", "apiserver_id_hash"}, + ) +) + +var registerMetrics sync.Once +var hashPool *sync.Pool + +func RegisterMetrics() { + registerMetrics.Do(func() { + hashPool = &sync.Pool{ + New: func() interface{} { + return sha256.New() + }, + } + legacyregistry.MustRegister(authorizationConfigAutomaticReloadsTotal) + legacyregistry.MustRegister(authorizationConfigAutomaticReloadLastTimestampSeconds) + }) +} + +func ResetMetricsForTest() { + authorizationConfigAutomaticReloadsTotal.Reset() + authorizationConfigAutomaticReloadLastTimestampSeconds.Reset() + legacyregistry.Reset() +} + +func RecordAuthorizationConfigAutomaticReloadFailure(apiServerID string) { + apiServerIDHash := getHash(apiServerID) + authorizationConfigAutomaticReloadsTotal.WithLabelValues("failure", apiServerIDHash).Inc() + authorizationConfigAutomaticReloadLastTimestampSeconds.WithLabelValues("failure", apiServerIDHash).SetToCurrentTime() +} + +func RecordAuthorizationConfigAutomaticReloadSuccess(apiServerID string) { + apiServerIDHash := getHash(apiServerID) + authorizationConfigAutomaticReloadsTotal.WithLabelValues("success", apiServerIDHash).Inc() + authorizationConfigAutomaticReloadLastTimestampSeconds.WithLabelValues("success", apiServerIDHash).SetToCurrentTime() +} + +func getHash(data string) string { + if len(data) == 0 { + return "" + } + h := hashPool.Get().(hash.Hash) + h.Reset() + h.Write([]byte(data)) + dataHash := fmt.Sprintf("sha256:%x", h.Sum(nil)) + hashPool.Put(h) + return dataHash +} diff --git a/pkg/server/options/authorizationconfig/metrics/metrics_test.go b/pkg/server/options/authorizationconfig/metrics/metrics_test.go new file mode 100644 index 000000000..1b7c7e0a6 --- /dev/null +++ b/pkg/server/options/authorizationconfig/metrics/metrics_test.go @@ -0,0 +1,109 @@ +/* +Copyright 2024 The Kubernetes 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 metrics + +import ( + "strings" + "testing" + + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/component-base/metrics/testutil" +) + +const ( + testAPIServerID = "testAPIServerID" + testAPIServerIDHash = "sha256:14f9d63e669337ac6bfda2e2162915ee6a6067743eddd4e5c374b572f951ff37" +) + +func TestRecordAuthorizationConfigAutomaticReloadFailure(t *testing.T) { + expectedValue := ` + # HELP apiserver_authorization_config_controller_automatic_reloads_total [ALPHA] Total number of automatic reloads of authorization configuration split by status and apiserver identity. + # TYPE apiserver_authorization_config_controller_automatic_reloads_total counter + apiserver_authorization_config_controller_automatic_reloads_total {apiserver_id_hash="sha256:14f9d63e669337ac6bfda2e2162915ee6a6067743eddd4e5c374b572f951ff37",status="failure"} 1 + ` + metrics := []string{ + namespace + "_" + subsystem + "_automatic_reloads_total", + } + + authorizationConfigAutomaticReloadsTotal.Reset() + RegisterMetrics() + + RecordAuthorizationConfigAutomaticReloadFailure(testAPIServerID) + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedValue), metrics...); err != nil { + t.Fatal(err) + } +} + +func TestRecordAuthorizationConfigAutomaticReloadSuccess(t *testing.T) { + expectedValue := ` + # HELP apiserver_authorization_config_controller_automatic_reloads_total [ALPHA] Total number of automatic reloads of authorization configuration split by status and apiserver identity. + # TYPE apiserver_authorization_config_controller_automatic_reloads_total counter + apiserver_authorization_config_controller_automatic_reloads_total {apiserver_id_hash="sha256:14f9d63e669337ac6bfda2e2162915ee6a6067743eddd4e5c374b572f951ff37",status="success"} 1 + ` + metrics := []string{ + namespace + "_" + subsystem + "_automatic_reloads_total", + } + + authorizationConfigAutomaticReloadsTotal.Reset() + RegisterMetrics() + + RecordAuthorizationConfigAutomaticReloadSuccess(testAPIServerID) + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedValue), metrics...); err != nil { + t.Fatal(err) + } +} + +func TestAuthorizationConfigAutomaticReloadLastTimestampSeconds(t *testing.T) { + testCases := []struct { + expectedValue string + resultLabel string + timestamp int64 + }{ + { + expectedValue: ` + # HELP apiserver_authorization_config_controller_automatic_reload_last_timestamp_seconds [ALPHA] Timestamp of the last automatic reload of authorization configuration split by status and apiserver identity. + # TYPE apiserver_authorization_config_controller_automatic_reload_last_timestamp_seconds gauge + apiserver_authorization_config_controller_automatic_reload_last_timestamp_seconds{apiserver_id_hash="sha256:14f9d63e669337ac6bfda2e2162915ee6a6067743eddd4e5c374b572f951ff37",status="failure"} 1.689101941e+09 + `, + resultLabel: "failure", + timestamp: 1689101941, + }, + { + expectedValue: ` + # HELP apiserver_authorization_config_controller_automatic_reload_last_timestamp_seconds [ALPHA] Timestamp of the last automatic reload of authorization configuration split by status and apiserver identity. + # TYPE apiserver_authorization_config_controller_automatic_reload_last_timestamp_seconds gauge + apiserver_authorization_config_controller_automatic_reload_last_timestamp_seconds{apiserver_id_hash="sha256:14f9d63e669337ac6bfda2e2162915ee6a6067743eddd4e5c374b572f951ff37",status="success"} 1.689101941e+09 + `, + resultLabel: "success", + timestamp: 1689101941, + }, + } + + metrics := []string{ + namespace + "_" + subsystem + "_automatic_reload_last_timestamp_seconds", + } + RegisterMetrics() + + for _, tc := range testCases { + authorizationConfigAutomaticReloadLastTimestampSeconds.Reset() + authorizationConfigAutomaticReloadLastTimestampSeconds.WithLabelValues(tc.resultLabel, testAPIServerIDHash).Set(float64(tc.timestamp)) + + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tc.expectedValue), metrics...); err != nil { + t.Fatal(err) + } + } +}