diff --git a/.dockerignore b/.dockerignore index d51f014..17f7604 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,5 @@ !go.mod !go.sum !index.html +# ignore local GOPATH=$(pwd)/.go/ +.go/ diff --git a/Dockerfile b/Dockerfile index 7b3b116..db1611d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,11 +2,12 @@ FROM golang:1.17.3 as builder WORKDIR /workspace -COPY . . +COPY go.mod go.sum ./ # cache deps before building and copying source so that we don't need to re-download as much # and so that source changes don't invalidate our downloaded layer RUN go mod download +COPY . . # Build RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o metrics-exporter . diff --git a/api/config.storageos.com/v1/constants.go b/api/config.storageos.com/v1/constants.go new file mode 100644 index 0000000..4aadfbc --- /dev/null +++ b/api/config.storageos.com/v1/constants.go @@ -0,0 +1,23 @@ +/* +Copyright 2022 Ondat. + +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 v1 + +const ( + // MetricsExporterConfigFileName is a mandatory key in the data field of the + // ConfigMap/storage-metrics-exporter. The associated value must be a valid MetricsExporterConfig + // serialized as YAML. + MetricsExporterConfigFileName = "config.yaml" +) diff --git a/api/config.storageos.com/v1/default.go b/api/config.storageos.com/v1/default.go new file mode 100644 index 0000000..3dceddf --- /dev/null +++ b/api/config.storageos.com/v1/default.go @@ -0,0 +1,26 @@ +/* +Copyright 2022 Ondat. + +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 v1 + +func (c *MetricsExporterConfig) Default() *MetricsExporterConfig { + if c.LogLevel == "" { + c.LogLevel = "info" + } + if c.Timeout == 0 { + c.Timeout = 10 + } + return c +} diff --git a/api/config.storageos.com/v1/metricsexporterconfig_types.go b/api/config.storageos.com/v1/metricsexporterconfig_types.go index cf21957..934be0a 100644 --- a/api/config.storageos.com/v1/metricsexporterconfig_types.go +++ b/api/config.storageos.com/v1/metricsexporterconfig_types.go @@ -28,6 +28,12 @@ type MetricsExporterConfig struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` + MetricsExporterConfigSpec `json:",inline"` +} + +// MetricsExporterConfigSpec represents the configuration options for the metrics-exporter. These fields shall +// be inlined in the StorageOSCluster.Spec.Metrics. +type MetricsExporterConfigSpec struct { // Verbosity of log messages. Accepts go.uber.org/zap log levels. // +kubebuilder:default:info // +kubebuilder:validation:Enum=debug;info;warn;error;dpanic;panic;fatal @@ -36,9 +42,22 @@ type MetricsExporterConfig struct { // Timeout in seconds to serve metrics. // +kubebuilder:default:10 // +kubebuilder:validation:Minimum=1 - Timeout int `json:"timeout"` + Timeout int `json:"timeout,omitempty"` + + // DisabledCollectors is a list of collectors that shall be disabled. By default, all are enabled. + DisabledCollectors []MetricsExporterCollector `json:"disabledCollectors,omitempty"` } +// MetricsExporterCollector is the name of a metrics collector in the metrics-exporter. +// +kubebuilder:validation:Enum=diskstats;filesystem +type MetricsExporterCollector string + +// All known metrics-exporter collectors are listed here. +const ( + MetricsExporterCollectorDiskStats MetricsExporterCollector = "diskstats" + MetricsExporterCollectorFileSystem MetricsExporterCollector = "filesystem" +) + func init() { SchemeBuilder.Register(&MetricsExporterConfig{}) } diff --git a/api/config.storageos.com/v1/zz_generated.deepcopy.go b/api/config.storageos.com/v1/zz_generated.deepcopy.go index cc9b7d3..0432922 100644 --- a/api/config.storageos.com/v1/zz_generated.deepcopy.go +++ b/api/config.storageos.com/v1/zz_generated.deepcopy.go @@ -14,6 +14,7 @@ func (in *MetricsExporterConfig) DeepCopyInto(out *MetricsExporterConfig) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.MetricsExporterConfigSpec.DeepCopyInto(&out.MetricsExporterConfigSpec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsExporterConfig. @@ -33,3 +34,23 @@ func (in *MetricsExporterConfig) DeepCopyObject() runtime.Object { } return nil } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MetricsExporterConfigSpec) DeepCopyInto(out *MetricsExporterConfigSpec) { + *out = *in + if in.DisabledCollectors != nil { + in, out := &in.DisabledCollectors, &out.DisabledCollectors + *out = make([]MetricsExporterCollector, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsExporterConfigSpec. +func (in *MetricsExporterConfigSpec) DeepCopy() *MetricsExporterConfigSpec { + if in == nil { + return nil + } + out := new(MetricsExporterConfigSpec) + in.DeepCopyInto(out) + return out +} diff --git a/collector.go b/collector.go index 61c1705..d7ffad2 100644 --- a/collector.go +++ b/collector.go @@ -7,6 +7,8 @@ import ( "github.com/google/uuid" "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" + + configondatv1 "github.com/ondat/metrics-exporter/api/config.storageos.com/v1" ) type Collector interface { @@ -77,3 +79,33 @@ func execute(log *zap.SugaredLogger, c Collector, ch chan<- prometheus.Metric, o } ch <- prometheus.MustNewConstMetric(scrapeSuccessMetric.desc, scrapeSuccessMetric.valueType, success, c.Name()) } + +func GetEnabledMetricsCollectors( + log *zap.SugaredLogger, + disabled []configondatv1.MetricsExporterCollector, +) []Collector { + var metricsCollectors []Collector + for name, collectorFactory := range map[configondatv1.MetricsExporterCollector](func() Collector){ + configondatv1.MetricsExporterCollectorDiskStats: func() Collector { return NewDiskStatsCollector() }, + configondatv1.MetricsExporterCollectorFileSystem: func() Collector { return NewFileSystemCollector() }, + } { + if IsCollectorDisabled(disabled, name) { + log.Infof("disabling %s collector", name) + continue + } + metricsCollectors = append(metricsCollectors, collectorFactory()) + } + return metricsCollectors +} + +func IsCollectorDisabled( + disabled []configondatv1.MetricsExporterCollector, + collector configondatv1.MetricsExporterCollector, +) bool { + for _, c := range disabled { + if c == collector { + return true + } + } + return false +} diff --git a/collector_test.go b/collector_test.go new file mode 100644 index 0000000..492d9ef --- /dev/null +++ b/collector_test.go @@ -0,0 +1,90 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + configondatv1 "github.com/ondat/metrics-exporter/api/config.storageos.com/v1" +) + +func TestGetEnabledMetricsCollectors(t *testing.T) { + tests := []struct { + name string + disable []configondatv1.MetricsExporterCollector + expectedEnabled []string + }{ + { + name: "all enabled", + disable: nil, + expectedEnabled: []string{ + "diskstats", + "filesystem", + }, + }, + + { + name: "disable filesystem", + disable: []configondatv1.MetricsExporterCollector{configondatv1.MetricsExporterCollectorFileSystem}, + expectedEnabled: []string{ + "diskstats", + }, + }, + + { + name: "disable diskstats", + disable: []configondatv1.MetricsExporterCollector{configondatv1.MetricsExporterCollectorDiskStats}, + expectedEnabled: []string{ + "filesystem", + }, + }, + + { + name: "disable both", + disable: []configondatv1.MetricsExporterCollector{ + configondatv1.MetricsExporterCollectorFileSystem, + configondatv1.MetricsExporterCollectorDiskStats, + }, + expectedEnabled: []string{}, + }, + + { + name: "disable both - reversed", + disable: []configondatv1.MetricsExporterCollector{ + configondatv1.MetricsExporterCollectorDiskStats, + configondatv1.MetricsExporterCollectorFileSystem, + }, + expectedEnabled: []string{}, + }, + + { + name: "ignore bad value", + disable: []configondatv1.MetricsExporterCollector{ + configondatv1.MetricsExporterCollector("bad"), + configondatv1.MetricsExporterCollectorFileSystem, + }, + expectedEnabled: []string{ + "diskstats", + }, + }, + } + + for _, tt := range tests { + var tt = tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + loggerConfig := zap.NewProductionConfig() + logger, _ := loggerConfig.Build() + log := logger.Sugar() + + collectors := GetEnabledMetricsCollectors(log, tt.disable) + names := make([]string, 0, len(collectors)) + for _, c := range collectors { + names = append(names, c.Name()) + } + require.ElementsMatch(t, tt.expectedEnabled, names) + }) + } +} diff --git a/config.go b/config.go index 42c9761..5fbab38 100644 --- a/config.go +++ b/config.go @@ -22,16 +22,10 @@ import ( "log" "os" - configondatv1 "github.com/ondat/metrics-exporter/api/config.storageos.com/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" -) -var ( - configDefaults = configondatv1.MetricsExporterConfig{ - LogLevel: "info", - Timeout: 10, - } + configondatv1 "github.com/ondat/metrics-exporter/api/config.storageos.com/v1" ) func readConfigFile(path string) (*configondatv1.MetricsExporterConfig, error) { @@ -42,11 +36,12 @@ func readConfigFile(path string) (*configondatv1.MetricsExporterConfig, error) { codecs := serializer.NewCodecFactory(scheme) - cfg := *configDefaults.DeepCopy() - if err = runtime.DecodeInto(codecs.UniversalDecoder(), content, &cfg); err != nil { + cfg := (&configondatv1.MetricsExporterConfig{}).Default() + if err = runtime.DecodeInto(codecs.UniversalDecoder(), content, cfg); err != nil { return nil, fmt.Errorf("could not decode file into runtime.Object: %v", err) } - return &cfg, nil + + return cfg, nil } func getConfigOrDie() (path string, cfg configondatv1.MetricsExporterConfig) { @@ -54,13 +49,15 @@ func getConfigOrDie() (path string, cfg configondatv1.MetricsExporterConfig) { var logLevelFlag string var timeoutFlag int + defaults := (&configondatv1.MetricsExporterConfig{}).Default() + flag.StringVar(&configFile, "config", "", "The exporter will load its initial configuration from this file. "+ "Omit this flag to use the default configuration values. "+ "Command-line flags override configuration from this file.") - flag.StringVar(&logLevelFlag, "log-level", configDefaults.LogLevel, + flag.StringVar(&logLevelFlag, "log-level", defaults.LogLevel, "Verbosity of log messages. Accepts go.uber.org/zap log levels.") - flag.IntVar(&timeoutFlag, "timeout", configDefaults.Timeout, "Timeout in seconds to serve metrics.") + flag.IntVar(&timeoutFlag, "timeout", defaults.Timeout, "Timeout in seconds to serve metrics.") flag.Parse() if len(configFile) > 0 { @@ -71,7 +68,7 @@ func getConfigOrDie() (path string, cfg configondatv1.MetricsExporterConfig) { } cfg = *parsedCfg } else { - cfg = *configDefaults.DeepCopy() + cfg = *defaults } // override defaults/configmap with the supplied flag values diff --git a/diskstats_collector.go b/diskstats_collector.go index 15b5a41..9c7a182 100644 --- a/diskstats_collector.go +++ b/diskstats_collector.go @@ -5,13 +5,15 @@ import ( "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" + + configondatv1 "github.com/ondat/metrics-exporter/api/config.storageos.com/v1" ) const ( // SECOND_IN_MILLISECONDS defines the number of seconds on a milliseconds. Used // to transform metrics that express a duration in milliseconds. SECOND_IN_MILLISECONDS = 1.0 / 1000.0 - DISKSTATS_COLLECTOR_NAME = "diskstats" + DISKSTATS_COLLECTOR_NAME = string(configondatv1.MetricsExporterCollectorDiskStats) ) // DiskStatsCollector implements the prometheus Collector interface diff --git a/filesystem_collector.go b/filesystem_collector.go index 9069528..4fce8c1 100644 --- a/filesystem_collector.go +++ b/filesystem_collector.go @@ -13,9 +13,11 @@ import ( "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" "golang.org/x/sys/unix" + + configondatv1 "github.com/ondat/metrics-exporter/api/config.storageos.com/v1" ) -const FILE_SYSTEM_COLLECTOR_NAME = "filesystem" +const FILE_SYSTEM_COLLECTOR_NAME = string(configondatv1.MetricsExporterCollectorFileSystem) var stuckMounts = make(map[string]struct{}) var stuckMountsMtx = &sync.Mutex{} diff --git a/main.go b/main.go index b022afe..cb59578 100644 --- a/main.go +++ b/main.go @@ -23,13 +23,14 @@ import ( "os" "time" - configondatv1 "github.com/ondat/metrics-exporter/api/config.storageos.com/v1" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.uber.org/zap" "go.uber.org/zap/zapcore" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + configondatv1 "github.com/ondat/metrics-exporter/api/config.storageos.com/v1" ) const ( @@ -72,9 +73,9 @@ func main() { } log.Debugf("Serve metrics timeout set to %d seconds", cfg.Timeout) - metricsCollectors := []Collector{ - NewDiskStatsCollector(), - NewFileSystemCollector(), + metricsCollectors := GetEnabledMetricsCollectors(log, cfg.DisabledCollectors) + if len(metricsCollectors) == 0 { + log.Fatal("there is nothing to do with all metrics collectors disabled") } prometheusRegistry := prometheus.NewRegistry() diff --git a/manifests/bundle.yaml b/manifests/bundle.yaml index bfb8f15..b5f4d53 100644 --- a/manifests/bundle.yaml +++ b/manifests/bundle.yaml @@ -61,7 +61,6 @@ metadata: labels: app: storageos app.kubernetes.io/component: metrics-exporter - service-discovery: storageos-metrics-exporter name: storageos-metrics-exporter namespace: storageos spec: diff --git a/manifests/service.yaml b/manifests/service.yaml index 32e9626..9b2d1fa 100644 --- a/manifests/service.yaml +++ b/manifests/service.yaml @@ -2,8 +2,6 @@ apiVersion: v1 kind: Service metadata: name: storageos-metrics-exporter - labels: - service-discovery: storageos-metrics-exporter spec: selector: category: metrics-exporter