Skip to content

Commit f8360a8

Browse files
committed
Add end-to-end testing to verify hot-reloading
Add end-to-end testing to verify hot-reloading for event-based changes stemming from the config file. Also, * sent in a doc fix that was missed earlier: https://github.com/kubernetes/kube-state-metrics/pull/1890/files#diff-380eca5a922c0ddbf67f04daefc6823e7ef0e197434d3a826d39c7063cdfa6d6R15, * updated fsnotify and viper dependencies (v1.6.0 and v1.14.0 respectively). Signed-off-by: Pranshu Srivastava <[email protected]>
1 parent 9860f46 commit f8360a8

File tree

9 files changed

+257
-303
lines changed

9 files changed

+257
-303
lines changed

docs/node-metrics.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
| kube_node_status_allocatable | Gauge | The allocatable for different resources of a node that are available for scheduling | `cpu`=&lt;core&gt; <br> `ephemeral_storage`=&lt;byte&gt; <br> `pods`=&lt;integer&gt; <br> `attachable_volumes_*`=&lt;byte&gt; <br> `hugepages_*`=&lt;byte&gt; <br> `memory`=&lt;byte&gt; |`node`=&lt;node-address&gt; <br> `resource`=&lt;resource-name&gt; <br> `unit`=&lt;resource-unit&gt;| STABLE |
1313
| kube_node_status_condition | Gauge | The condition of a cluster node | |`node`=&lt;node-address&gt; <br> `condition`=&lt;node-condition&gt; <br> `status`=&lt;true\|false\|unknown&gt; | STABLE |
1414
| kube_node_created | Gauge | Unix creation timestamp | seconds |`node`=&lt;node-address&gt;| STABLE |
15-
| kube_node_deletion_timestamp | Gauge | Unix creation timestamp | seconds |`node`=&lt;node-address&gt;| STABLE |
15+
| kube_node_deletion_timestamp | Gauge | Unix creation timestamp | seconds |`node`=&lt;node-address&gt;| EXPERIMENTAL |

go.mod

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ require (
44
github.com/brancz/gojsontoyaml v0.1.0
55
github.com/campoy/embedmd v1.0.0
66
github.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0
7-
github.com/fsnotify/fsnotify v1.5.4
7+
github.com/fsnotify/fsnotify v1.6.0
88
github.com/gobuffalo/flect v0.3.0
99
github.com/google/go-cmp v0.5.9
1010
github.com/google/go-jsonnet v0.19.1
@@ -16,7 +16,7 @@ require (
1616
github.com/prometheus/exporter-toolkit v0.8.1
1717
github.com/robfig/cron/v3 v3.0.1
1818
github.com/spf13/cobra v1.6.1
19-
github.com/spf13/viper v1.13.0
19+
github.com/spf13/viper v1.14.0
2020
github.com/stretchr/testify v1.8.1
2121
golang.org/x/perf v0.0.0-20220920022801-e8d778a60d07
2222
gopkg.in/yaml.v3 v3.0.1
@@ -31,7 +31,8 @@ require (
3131
)
3232

3333
require (
34-
cloud.google.com/go/compute v1.7.0 // indirect
34+
cloud.google.com/go/compute v1.12.1 // indirect
35+
cloud.google.com/go/compute/metadata v0.2.1 // indirect
3536
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
3637
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
3738
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
@@ -84,19 +85,19 @@ require (
8485
github.com/pkg/errors v0.9.1 // indirect
8586
github.com/pmezard/go-difflib v1.0.0 // indirect
8687
github.com/prometheus/procfs v0.8.0 // indirect
87-
github.com/spf13/afero v1.8.2 // indirect
88+
github.com/spf13/afero v1.9.2 // indirect
8889
github.com/spf13/cast v1.5.0 // indirect
8990
github.com/spf13/jwalterweatherman v1.1.0 // indirect
9091
github.com/spf13/pflag v1.0.5 // indirect
9192
github.com/subosito/gotenv v1.4.1 // indirect
9293
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect
93-
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
94-
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect
94+
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect
95+
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
9596
golang.org/x/sync v0.1.0 // indirect
9697
golang.org/x/sys v0.1.0 // indirect
9798
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
98-
golang.org/x/text v0.3.7 // indirect
99-
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
99+
golang.org/x/text v0.4.0 // indirect
100+
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
100101
google.golang.org/appengine v1.6.7 // indirect
101102
google.golang.org/protobuf v1.28.1 // indirect
102103
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect

go.sum

Lines changed: 19 additions & 211 deletions
Large diffs are not rendered by default.

internal/wrapper.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package internal
18+
19+
import (
20+
"context"
21+
"errors"
22+
"os"
23+
"path/filepath"
24+
"strings"
25+
"time"
26+
27+
"github.com/fsnotify/fsnotify"
28+
"github.com/spf13/viper"
29+
"gopkg.in/yaml.v3"
30+
"k8s.io/klog/v2"
31+
32+
"k8s.io/kube-state-metrics/v2/pkg/app"
33+
"k8s.io/kube-state-metrics/v2/pkg/customresource"
34+
"k8s.io/kube-state-metrics/v2/pkg/customresourcestate"
35+
"k8s.io/kube-state-metrics/v2/pkg/options"
36+
)
37+
38+
// RunKubeStateMetricsWrapper is a wrapper around KSM, delegated to the root command.
39+
func RunKubeStateMetricsWrapper(opts *options.Options) {
40+
var factories []customresource.RegistryFactory
41+
if config, set := resolveCustomResourceConfig(opts); set {
42+
crf, err := customresourcestate.FromConfig(config)
43+
if err != nil {
44+
klog.ErrorS(err, "Parsing from Custom Resource State Metrics file failed")
45+
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
46+
}
47+
factories = append(factories, crf...)
48+
}
49+
50+
KSMRunOrDie := func(ctx context.Context) {
51+
if err := app.RunKubeStateMetricsWrapper(ctx, opts, factories...); err != nil {
52+
klog.ErrorS(err, "Failed to run kube-state-metrics")
53+
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
54+
}
55+
}
56+
ctx, cancel := context.WithCancel(context.Background())
57+
if file := options.GetOptsConfigFile(*opts); file != "" {
58+
viper.SetConfigType("yaml")
59+
viper.SetConfigFile(file)
60+
if err := viper.ReadInConfig(); err != nil {
61+
if errors.Is(err, viper.ConfigFileNotFoundError{}) {
62+
klog.ErrorS(err, "Options configuration file not found", "file", file)
63+
} else {
64+
klog.ErrorS(err, "Error reading options configuration file", "file", file)
65+
}
66+
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
67+
}
68+
viper.OnConfigChange(func(e fsnotify.Event) {
69+
klog.Infof("Changes detected: %s\n", e.Name)
70+
cancel()
71+
// Wait for the ports to be released.
72+
<-time.After(3 * time.Second)
73+
ctx, cancel = context.WithCancel(context.Background())
74+
go KSMRunOrDie(ctx)
75+
})
76+
viper.WatchConfig()
77+
}
78+
klog.Infoln("Starting kube-state-metrics")
79+
KSMRunOrDie(ctx)
80+
select {}
81+
}
82+
83+
func resolveCustomResourceConfig(opts *options.Options) (customresourcestate.ConfigDecoder, bool) {
84+
if s := opts.CustomResourceConfig; s != "" {
85+
return yaml.NewDecoder(strings.NewReader(s)), true
86+
}
87+
if file := opts.CustomResourceConfigFile; file != "" {
88+
f, err := os.Open(filepath.Clean(file))
89+
if err != nil {
90+
klog.ErrorS(err, "Custom Resource State Metrics file could not be opened")
91+
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
92+
}
93+
return yaml.NewDecoder(f), true
94+
}
95+
return nil, false
96+
}

main.go

Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,18 @@ limitations under the License.
1717
package main
1818

1919
import (
20-
"context"
21-
"errors"
22-
"os"
23-
"path/filepath"
24-
"strings"
25-
"time"
26-
27-
"github.com/fsnotify/fsnotify"
2820
"github.com/spf13/cobra"
29-
"github.com/spf13/viper"
30-
"gopkg.in/yaml.v3"
3121
"k8s.io/klog/v2"
3222

33-
"k8s.io/kube-state-metrics/v2/pkg/app"
34-
"k8s.io/kube-state-metrics/v2/pkg/customresource"
35-
"k8s.io/kube-state-metrics/v2/pkg/customresourcestate"
23+
"k8s.io/kube-state-metrics/v2/internal"
3624
"k8s.io/kube-state-metrics/v2/pkg/options"
3725
)
3826

3927
func main() {
4028
opts := options.NewOptions()
4129
cmd := options.InitCommand
4230
cmd.Run = func(cmd *cobra.Command, args []string) {
43-
RunKubeStateMetricsWrapper(opts)
31+
internal.RunKubeStateMetricsWrapper(opts)
4432
}
4533
opts.AddFlags(cmd)
4634

@@ -53,63 +41,3 @@ func main() {
5341
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
5442
}
5543
}
56-
57-
// RunKubeStateMetricsWrapper is a wrapper around KSM, delegated to the root command.
58-
func RunKubeStateMetricsWrapper(opts *options.Options) {
59-
var factories []customresource.RegistryFactory
60-
if config, set := resolveCustomResourceConfig(opts); set {
61-
crf, err := customresourcestate.FromConfig(config)
62-
if err != nil {
63-
klog.ErrorS(err, "Parsing from Custom Resource State Metrics file failed")
64-
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
65-
}
66-
factories = append(factories, crf...)
67-
}
68-
69-
KSMRunOrDie := func(ctx context.Context) {
70-
if err := app.RunKubeStateMetricsWrapper(ctx, opts, factories...); err != nil {
71-
klog.ErrorS(err, "Failed to run kube-state-metrics")
72-
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
73-
}
74-
}
75-
ctx, cancel := context.WithCancel(context.Background())
76-
if file := options.GetOptsConfigFile(*opts); file != "" {
77-
viper.SetConfigType("yaml")
78-
viper.SetConfigFile(file)
79-
if err := viper.ReadInConfig(); err != nil {
80-
if errors.Is(err, viper.ConfigFileNotFoundError{}) {
81-
klog.ErrorS(err, "Options configuration file not found", "file", file)
82-
} else {
83-
klog.ErrorS(err, "Error reading options configuration file", "file", file)
84-
}
85-
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
86-
}
87-
viper.OnConfigChange(func(e fsnotify.Event) {
88-
klog.Infof("Changes detected: %s\n", e.Name)
89-
cancel()
90-
// Wait for the ports to be released.
91-
<-time.After(3 * time.Second)
92-
ctx, cancel = context.WithCancel(context.Background())
93-
go KSMRunOrDie(ctx)
94-
})
95-
viper.WatchConfig()
96-
}
97-
klog.Infoln("Starting kube-state-metrics")
98-
KSMRunOrDie(ctx)
99-
select {}
100-
}
101-
102-
func resolveCustomResourceConfig(opts *options.Options) (customresourcestate.ConfigDecoder, bool) {
103-
if s := opts.CustomResourceConfig; s != "" {
104-
return yaml.NewDecoder(strings.NewReader(s)), true
105-
}
106-
if file := opts.CustomResourceConfigFile; file != "" {
107-
f, err := os.Open(filepath.Clean(file))
108-
if err != nil {
109-
klog.ErrorS(err, "Custom Resource State Metrics file could not be opened")
110-
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
111-
}
112-
return yaml.NewDecoder(f), true
113-
}
114-
return nil, false
115-
}

pkg/options/options.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ type Options struct {
5757
UseAPIServerCache bool `yaml:"use_api_server_cache"`
5858
Version bool `yaml:"version"`
5959

60-
optsConfigFile string
60+
Config string
6161

6262
cmd *cobra.Command
6363
}
6464

6565
// GetOptsConfigFile is the getter for --options-config-file value.
6666
func GetOptsConfigFile(opt Options) string {
67-
return opt.optsConfigFile
67+
return opt.Config
6868
}
6969

7070
// NewOptions returns a new instance of `Options`.
@@ -137,7 +137,7 @@ func (o *Options) AddFlags(cmd *cobra.Command) {
137137
o.cmd.Flags().StringVar(&o.Pod, "pod", "", "Name of the pod that contains the kube-state-metrics container. "+autoshardingNotice)
138138
o.cmd.Flags().StringVar(&o.TLSConfig, "tls-config", "", "Path to the TLS configuration file")
139139
o.cmd.Flags().StringVar(&o.TelemetryHost, "telemetry-host", "::", `Host to expose kube-state-metrics self metrics on.`)
140-
o.cmd.Flags().StringVar(&o.optsConfigFile, "config", "", "Path to the kube-state-metrics options config file")
140+
o.cmd.Flags().StringVar(&o.Config, "config", "", "Path to the kube-state-metrics options config file")
141141
o.cmd.Flags().StringVar((*string)(&o.Node), "node", "", "Name of the node that contains the kube-state-metrics pod. Most likely it should be passed via the downward API. This is used for daemonset sharding. Only available for resources (pod metrics) that support spec.nodeName fieldSelector. This is experimental.")
142142
o.cmd.Flags().Var(&o.AnnotationsAllowList, "metric-annotations-allowlist", "Comma-separated list of Kubernetes annotations keys that will be used in the resource' labels metric. By default the metric contains only name and namespace labels. To include additional annotations provide a list of resource names in their plural form and Kubernetes annotation keys you would like to allow for them (Example: '=namespaces=[kubernetes.io/team,...],pods=[kubernetes.io/team],...)'. A single '*' can be provided per resource instead to allow any annotations, but that has severe performance implications (Example: '=pods=[*]').")
143143
o.cmd.Flags().Var(&o.LabelsAllowList, "metric-labels-allowlist", "Comma-separated list of additional Kubernetes label keys that will be used in the resource' labels metric. By default the metric contains only name and namespace labels. To include additional labels provide a list of resource names in their plural form and Kubernetes label keys you would like to allow for them (Example: '=namespaces=[k8s-label-1,k8s-label-n,...],pods=[app],...)'. A single '*' can be provided per resource instead to allow any labels, but that has severe performance implications (Example: '=pods=[*]'). Additionally, an asterisk (*) can be provided as a key, which will resolve to all resources, i.e., assuming '--resources=deployments,pods', '=*=[*]' will resolve to '=deployments=[*],pods=[*]'.")

pkg/options/options_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,18 @@ func TestOptionsParse(t *testing.T) {
4848
opts.AddFlags(InitCommand)
4949

5050
for _, test := range tests {
51+
t.Run(test.Desc, func(t *testing.T) {
52+
os.Args = test.Args
5153

52-
os.Args = test.Args
54+
err := opts.Parse()
5355

54-
err := opts.Parse()
55-
if err != nil {
56-
if !test.ExpectsError {
56+
if !test.ExpectsError && err != nil {
5757
t.Errorf("Error for test with description: %s: %v", test.Desc, err.Error())
5858
}
59-
}
59+
60+
if test.ExpectsError && err == nil {
61+
t.Errorf("Expected error for test with description: %s", test.Desc)
62+
}
63+
})
6064
}
6165
}

tests/e2e.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ echo "kube-state-metrics is up and running"
155155
echo "start e2e test for kube-state-metrics"
156156
KSM_HTTP_METRICS_URL='http://localhost:8001/api/v1/namespaces/kube-system/services/kube-state-metrics:http-metrics/proxy'
157157
KSM_TELEMETRY_URL='http://localhost:8001/api/v1/namespaces/kube-system/services/kube-state-metrics:telemetry/proxy'
158-
go test -v ./tests/e2e/ --ksm-http-metrics-url=${KSM_HTTP_METRICS_URL} --ksm-telemetry-url=${KSM_TELEMETRY_URL}
158+
go test -v ./tests/e2e/main_test.go --ksm-http-metrics-url=${KSM_HTTP_METRICS_URL} --ksm-telemetry-url=${KSM_TELEMETRY_URL}
159+
go test -v ./tests/e2e/hot-reload_test.go
159160

160161
mkdir -p ${KUBE_STATE_METRICS_LOG_DIR}
161162

0 commit comments

Comments
 (0)