diff --git a/cmd/options/options.go b/cmd/options/options.go index 984f376c..143acfa7 100644 --- a/cmd/options/options.go +++ b/cmd/options/options.go @@ -24,18 +24,15 @@ import ( "net/url" "github.com/spf13/pflag" + + "k8s.io/node-problem-detector/pkg/problemdaemon" + "k8s.io/node-problem-detector/pkg/types" ) // NodeProblemDetectorOptions contains node problem detector command line and application options. type NodeProblemDetectorOptions struct { // command line options - // SystemLogMonitorConfigPaths specifies the list of paths to system log monitor configuration - // files. - SystemLogMonitorConfigPaths []string - // CustomPluginMonitorConfigPaths specifies the list of paths to custom plugin monitor configuration - // files. - CustomPluginMonitorConfigPaths []string // PrintVersion is the flag determining whether version information is printed. PrintVersion bool // HostnameOverride specifies custom node name used to override hostname. @@ -53,6 +50,17 @@ type NodeProblemDetectorOptions struct { // ApiServerOverride is the custom URI used to connect to Kubernetes ApiServer. ApiServerOverride string + // problem daemon options + + // SystemLogMonitorConfigPaths specifies the list of paths to system log monitor configuration + // files. + SystemLogMonitorConfigPaths []string + // CustomPluginMonitorConfigPaths specifies the list of paths to custom plugin monitor configuration + // files. + CustomPluginMonitorConfigPaths []string + // MonitorConfigPaths specifies the list of paths to configuration files for each monitor. + MonitorConfigPaths types.ProblemDaemonConfigPathMap + // application options // NodeName is the node name used to communicate with Kubernetes ApiServer. @@ -60,7 +68,7 @@ type NodeProblemDetectorOptions struct { } func NewNodeProblemDetectorOptions() *NodeProblemDetectorOptions { - return &NodeProblemDetectorOptions{} + return &NodeProblemDetectorOptions{MonitorConfigPaths: types.ProblemDaemonConfigPathMap{}} } // AddFlags adds node problem detector command line options to pflag. @@ -79,6 +87,17 @@ func (npdo *NodeProblemDetectorOptions) AddFlags(fs *pflag.FlagSet) { 20256, "The port to bind the node problem detector server. Use 0 to disable.") fs.StringVar(&npdo.ServerAddress, "address", "127.0.0.1", "The address to bind the node problem detector server.") + + for _, problemDaemonName := range problemdaemon.GetProblemDaemonNames() { + npdo.MonitorConfigPaths[problemDaemonName] = &[]string{} + fs.StringSliceVar( + npdo.MonitorConfigPaths[problemDaemonName], + "config."+string(problemDaemonName), + []string{}, + fmt.Sprintf("Comma separated configurations for %v monitor. %v", + problemDaemonName, + problemdaemon.GetProblemDaemonHandlerOrDie(problemDaemonName).CmdOptionDescription)) + } } // ValidOrDie validates node problem detector command line options. @@ -90,6 +109,12 @@ func (npdo *NodeProblemDetectorOptions) ValidOrDie() { if len(npdo.SystemLogMonitorConfigPaths) == 0 && len(npdo.CustomPluginMonitorConfigPaths) == 0 { panic(fmt.Sprintf("Either --system-log-monitors or --custom-plugin-monitors is required")) } + + for problemDaemonName, configs := range npdo.MonitorConfigPaths { + if configs == nil { + panic(fmt.Sprintf("nil config for problem daemon %q. This should never happen, might indicates bug in pflag.", problemDaemonName)) + } + } } // SetNodeNameOrDie sets `NodeName` field with valid value. diff --git a/pkg/problemdaemon/problem_daemon.go b/pkg/problemdaemon/problem_daemon.go new file mode 100644 index 00000000..79a1124a --- /dev/null +++ b/pkg/problemdaemon/problem_daemon.go @@ -0,0 +1,73 @@ +/* +Copyright 2019 The Kubernetes Authors All rights reserved. + +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 problemdaemon + +import ( + "fmt" + + "github.com/golang/glog" + + "k8s.io/node-problem-detector/pkg/types" +) + +var ( + handlers = make(map[types.ProblemDaemonType]types.ProblemDaemonHandler) +) + +// Register registers a problem daemon factory method, which will be used to create the problem daemon. +func Register(problemDaemonType types.ProblemDaemonType, handler types.ProblemDaemonHandler) { + handlers[problemDaemonType] = handler +} + +// GetProblemDaemonNames retrieves all available problem daemon types. +func GetProblemDaemonNames() []types.ProblemDaemonType { + problemDaemonTypes := []types.ProblemDaemonType{} + for problemDaemonType := range handlers { + problemDaemonTypes = append(problemDaemonTypes, problemDaemonType) + } + return problemDaemonTypes +} + +// GetProblemDaemonHandlerOrDie retrieves the ProblemDaemonHandler for a specific type of problem daemon, panic if error occurs.. +func GetProblemDaemonHandlerOrDie(problemDaemonType types.ProblemDaemonType) types.ProblemDaemonHandler { + handler, ok := handlers[problemDaemonType] + if !ok { + panic(fmt.Sprintf("Problem daemon handler for %v does not exist", problemDaemonType)) + } + return handler +} + +// NewProblemDaemons creates all problem daemons based on the configurations provided. +func NewProblemDaemons(monitorConfigPaths types.ProblemDaemonConfigPathMap) []types.Monitor { + problemDaemonMap := make(map[string]types.Monitor) + for problemDaemonType, configs := range monitorConfigPaths { + for _, config := range *configs { + if _, ok := problemDaemonMap[config]; ok { + // Skip the config if it's duplicated. + glog.Warningf("Duplicated problem daemon configuration %q", config) + continue + } + problemDaemonMap[config] = handlers[problemDaemonType].CreateProblemDaemonOrDie(config) + } + } + + problemDaemons := []types.Monitor{} + for _, problemDaemon := range problemDaemonMap { + problemDaemons = append(problemDaemons, problemDaemon) + } + return problemDaemons +} diff --git a/pkg/problemdaemon/problem_daemon_test.go b/pkg/problemdaemon/problem_daemon_test.go new file mode 100644 index 00000000..2911b69d --- /dev/null +++ b/pkg/problemdaemon/problem_daemon_test.go @@ -0,0 +1,72 @@ +/* +Copyright 2019 The Kubernetes Authors All rights reserved. + +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 problemdaemon + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "k8s.io/node-problem-detector/pkg/types" +) + +func TestRegistration(t *testing.T) { + fooMonitorFactory := func(configPath string) types.Monitor { + return nil + } + fooMonitorHandler := types.ProblemDaemonHandler{ + CreateProblemDaemonOrDie: fooMonitorFactory, + CmdOptionDescription: "foo option", + } + + barMonitorFactory := func(configPath string) types.Monitor { + return nil + } + barMonitorHandler := types.ProblemDaemonHandler{ + CreateProblemDaemonOrDie: barMonitorFactory, + CmdOptionDescription: "bar option", + } + + Register("foo", fooMonitorHandler) + Register("bar", barMonitorHandler) + + expectedProblemDaemonNames := []types.ProblemDaemonType{"foo", "bar"} + problemDaemonNames := GetProblemDaemonNames() + + assert.ElementsMatch(t, expectedProblemDaemonNames, problemDaemonNames) + assert.Equal(t, "foo option", GetProblemDaemonHandlerOrDie("foo").CmdOptionDescription) + assert.Equal(t, "bar option", GetProblemDaemonHandlerOrDie("bar").CmdOptionDescription) + + handlers = make(map[types.ProblemDaemonType]types.ProblemDaemonHandler) +} + +func TestGetProblemDaemonHandlerOrDie(t *testing.T) { + fooMonitorFactory := func(configPath string) types.Monitor { + return nil + } + fooMonitorHandler := types.ProblemDaemonHandler{ + CreateProblemDaemonOrDie: fooMonitorFactory, + CmdOptionDescription: "foo option", + } + + Register("foo", fooMonitorHandler) + + assert.NotPanics(t, func() { GetProblemDaemonHandlerOrDie("foo") }) + assert.Panics(t, func() { GetProblemDaemonHandlerOrDie("bar") }) + + handlers = make(map[types.ProblemDaemonType]types.ProblemDaemonHandler) +} diff --git a/pkg/types/types.go b/pkg/types/types.go index 3acdca79..a06150be 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -113,3 +113,20 @@ type Exporter interface { // Export problems to the control plane. ExportProblems(*Status) } + +// ProblemDaemonType is the type of the problem daemon. +// One type of problem daemon may be used to initialize multiple problem daemon instances. +type ProblemDaemonType string + +// ProblemDaemonConfigPathMap represents configurations on all types of problem daemons: +// 1) Each key represents a type of problem daemon. +// 2) Each value represents the config file paths to that type of problem daemon. +type ProblemDaemonConfigPathMap map[ProblemDaemonType]*[]string + +// ProblemDaemonHandler represents the initialization handler for a type problem daemon. +type ProblemDaemonHandler struct { + // CreateProblemDaemonOrDie initializes a problem daemon, panic if error occurs. + CreateProblemDaemonOrDie func(string) Monitor + // CmdOptionDescription explains how to configure the problem daemon from command line arguments. + CmdOptionDescription string +}