diff --git a/cmd/check_interface_metrics.go b/cmd/check_interface_metrics.go index e9b9ae2..f129d74 100644 --- a/cmd/check_interface_metrics.go +++ b/cmd/check_interface_metrics.go @@ -2,6 +2,7 @@ package cmd import ( "github.com/inexio/thola/internal/request" + "github.com/inexio/thola/internal/utility" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -11,6 +12,8 @@ func init() { checkCMD.AddCommand(checkInterfaceMetricsCMD) checkInterfaceMetricsCMD.Flags().Bool("print-interfaces", false, "Print interfaces to plugin output") + checkInterfaceMetricsCMD.Flags().String("ifDescr-regex", "", "Apply a regex on the ifDescr of the interfaces. Use it together with the 'ifDescr-regex-replace' flag") + checkInterfaceMetricsCMD.Flags().String("ifDescr-regex-replace", "", "Apply a regex on the ifDescr of the interfaces. Use it together with the 'ifDescr-regex' flag") checkInterfaceMetricsCMD.Flags().StringSlice("ifType-filter", []string{}, "Filter out interfaces which ifType equals the given types") checkInterfaceMetricsCMD.Flags().StringSlice("ifName-filter", []string{}, "Filter out interfaces which ifName matches the given regex") checkInterfaceMetricsCMD.Flags().StringSlice("ifDescr-filter", []string{}, "Filter out interfaces which ifDescription matches the given regex") @@ -25,6 +28,14 @@ var checkInterfaceMetricsCMD = &cobra.Command{ if err != nil { log.Fatal().Err(err).Msg("print-interfaces needs to be a boolean") } + ifDescrRegex, err := cmd.Flags().GetString("ifDescr-regex") + if err != nil { + log.Fatal().Err(err).Msg("ifDescr-regex needs to be a string") + } + ifDescrRegexReplace, err := cmd.Flags().GetString("ifDescr-regex-replace") + if err != nil { + log.Fatal().Err(err).Msg("ifDescr-regex-replace needs to be a string") + } ifTypeFilter, err := cmd.Flags().GetStringSlice("ifType-filter") if err != nil { log.Fatal().Err(err).Msg("ifType-filter needs to be a string") @@ -37,13 +48,18 @@ var checkInterfaceMetricsCMD = &cobra.Command{ if err != nil { log.Fatal().Err(err).Msg("ifDescr-filter needs to be a string") } + + var nullString *string r := request.CheckInterfaceMetricsRequest{ - CheckDeviceRequest: getCheckDeviceRequest(args[0]), - PrintInterfaces: printInterfaces, - IfTypeFilter: ifTypeFilter, - IfNameFilter: ifNameFilter, - IfDescrFilter: ifDescrFilter, + CheckDeviceRequest: getCheckDeviceRequest(args[0]), + PrintInterfaces: printInterfaces, + IfDescrRegex: utility.IfThenElse(cmd.Flags().Changed("ifDescr-regex"), &ifDescrRegex, nullString).(*string), + IfDescrRegexReplace: utility.IfThenElse(cmd.Flags().Changed("ifDescr-regex-replace"), &ifDescrRegexReplace, nullString).(*string), + IfTypeFilter: ifTypeFilter, + IfNameFilter: ifNameFilter, + IfDescrFilter: ifDescrFilter, } + handleRequest(&r) }, } diff --git a/config/device-classes/generic/planetos.yaml b/config/device-classes/generic/planetos.yaml index 983120f..b326f78 100644 --- a/config/device-classes/generic/planetos.yaml +++ b/config/device-classes/generic/planetos.yaml @@ -19,17 +19,4 @@ identify: - type: modify modify_method: regexSubmatch regex: 'PLANET ([^\s]+)\s' - format: "$1" - -components: - interfaces: - properties: - detection: snmpwalk - values: - ifDescr: - oid: 1.3.6.1.2.1.2.2.1.2 - operators: - - type: modify - modify_method: regexReplace - regex: '\s+' - replace: ' ' \ No newline at end of file + format: "$1" \ No newline at end of file diff --git a/internal/request/check_interface_metrics_request.go b/internal/request/check_interface_metrics_request.go index d667ab8..d9ecf96 100644 --- a/internal/request/check_interface_metrics_request.go +++ b/internal/request/check_interface_metrics_request.go @@ -1,14 +1,40 @@ package request +import ( + "context" + "github.com/pkg/errors" + "regexp" +) + // CheckInterfaceMetricsRequest // // CheckInterfaceRequest is a the request struct for the check interface metrics request. // // swagger:model type CheckInterfaceMetricsRequest struct { - PrintInterfaces bool `yaml:"print_interfaces" json:"print_interfaces" xml:"print_interfaces"` - IfTypeFilter []string `yaml:"ifType_filter" json:"ifType_filter" xml:"ifType_filter"` - IfNameFilter []string `yaml:"ifName_filter" json:"ifName_filter" xml:"ifName_filter"` - IfDescrFilter []string `yaml:"ifDescr_filter" json:"ifDescr_filter" xml:"ifDescr_filter"` + PrintInterfaces bool `yaml:"print_interfaces" json:"print_interfaces" xml:"print_interfaces"` + IfDescrRegex *string `yaml:"ifDescr_regex" json:"ifDescr_regex" xml:"ifDescr_regex"` + ifDescrRegex *regexp.Regexp + IfDescrRegexReplace *string `yaml:"ifDescr_regex_replace" json:"ifDescr_regex_replace" xml:"ifDescr_regex_replace"` + IfTypeFilter []string `yaml:"ifType_filter" json:"ifType_filter" xml:"ifType_filter"` + IfNameFilter []string `yaml:"ifName_filter" json:"ifName_filter" xml:"ifName_filter"` + IfDescrFilter []string `yaml:"ifDescr_filter" json:"ifDescr_filter" xml:"ifDescr_filter"` CheckDeviceRequest } + +func (r *CheckInterfaceMetricsRequest) validate(ctx context.Context) error { + if r.IfDescrRegex != nil && r.IfDescrRegexReplace == nil || + r.IfDescrRegex == nil && r.IfDescrRegexReplace != nil { + return errors.New("'ifDescr-regex' and 'ifDescr-regex-replace' must be set together") + } + + if r.IfDescrRegex != nil { + regex, err := regexp.Compile(*r.IfDescrRegex) + if err != nil { + return errors.Wrap(err, "compiling ifDescrRegex failed") + } + r.ifDescrRegex = regex + } + + return r.CheckDeviceRequest.validate(ctx) +} diff --git a/internal/request/check_interface_metrics_request_process.go b/internal/request/check_interface_metrics_request_process.go index 5dab4a3..1f547de 100644 --- a/internal/request/check_interface_metrics_request_process.go +++ b/internal/request/check_interface_metrics_request_process.go @@ -27,21 +27,35 @@ type interfaceCheckOutput struct { func (r *CheckInterfaceMetricsRequest) process(ctx context.Context) (Response, error) { r.init() - readInterfacesResponse, err := r.getData(ctx) - if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while processing read interfaces request", true) { + readInterfacesRequest := ReadInterfacesRequest{ReadRequest{r.BaseRequest}} + response, err := readInterfacesRequest.process(ctx) + if err != nil { + return nil, err + } + + readInterfacesResponse := response.(*ReadInterfacesResponse) + + err = r.normalizeInterfaces(readInterfacesResponse.Interfaces) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while normalizing interfaces", true) { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + + interfaces, err := r.filterInterfaces(readInterfacesResponse.Interfaces) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while filtering interfaces", true) { r.mon.PrintPerformanceData(false) return &CheckResponse{r.mon.GetInfo()}, nil } - err = addCheckInterfacePerformanceData(readInterfacesResponse.Interfaces, r.mon) + err = addCheckInterfacePerformanceData(interfaces, r.mon) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data", true) { r.mon.PrintPerformanceData(false) return &CheckResponse{r.mon.GetInfo()}, nil } if r.PrintInterfaces { - var interfaces []interfaceCheckOutput - for _, interf := range readInterfacesResponse.Interfaces { + var interfaceOutput []interfaceCheckOutput + for _, interf := range interfaces { var index *string if interf.IfIndex != nil { i := fmt.Sprint(*interf.IfIndex) @@ -60,9 +74,9 @@ func (r *CheckInterfaceMetricsRequest) process(ctx context.Context) (Response, e SubType: interf.SubType, } - interfaces = append(interfaces, x) + interfaceOutput = append(interfaceOutput, x) } - output, err := parser.Parse(interfaces, "json") + output, err := parser.Parse(interfaceOutput, "json") if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while marshalling output", true) { r.mon.PrintPerformanceData(false) return &CheckResponse{r.mon.GetInfo()}, nil @@ -73,18 +87,10 @@ func (r *CheckInterfaceMetricsRequest) process(ctx context.Context) (Response, e return &CheckResponse{r.mon.GetInfo()}, nil } -func (r *CheckInterfaceMetricsRequest) getData(ctx context.Context) (*ReadInterfacesResponse, error) { - readInterfacesRequest := ReadInterfacesRequest{ReadRequest{r.BaseRequest}} - response, err := readInterfacesRequest.process(ctx) - if err != nil { - return nil, err - } - - readInterfacesResponse := response.(*ReadInterfacesResponse) - +func (r *CheckInterfaceMetricsRequest) filterInterfaces(interfaces []device.Interface) ([]device.Interface, error) { var filterIndices []int out: - for i, interf := range readInterfacesResponse.Interfaces { + for i, interf := range interfaces { for _, filter := range r.IfTypeFilter { if interf.IfType != nil && *interf.IfType == filter { filterIndices = append(filterIndices, i) @@ -117,9 +123,9 @@ out: } } - readInterfacesResponse.Interfaces = filterInterfaces(readInterfacesResponse.Interfaces, filterIndices, 0) + interfaces = filterInterfaces(interfaces, filterIndices, 0) - return readInterfacesResponse, nil + return interfaces, nil } func filterInterfaces(interfaces []device.Interface, toRemove []int, alreadyRemoved int) []device.Interface { @@ -129,9 +135,24 @@ func filterInterfaces(interfaces []device.Interface, toRemove []int, alreadyRemo return append(interfaces[:toRemove[0]-alreadyRemoved], filterInterfaces(interfaces[toRemove[0]+1-alreadyRemoved:], toRemove[1:], toRemove[0]+1)...) } -func addCheckInterfacePerformanceData(interfaces []device.Interface, r *monitoringplugin.Response) error { - ifDescriptions := make(map[string]*device.Interface) +func (r *CheckInterfaceMetricsRequest) normalizeInterfaces(interfaces []device.Interface) error { + for i, interf := range interfaces { + // if the ifDescr is empty, use the ifIndex as the ifDescr and therefore also as the label for the metrics + if interf.IfDescr == nil { + if interf.IfIndex == nil { + return errors.New("interface does not have an ifDescription and ifIndex") + } + index := fmt.Sprint(*interfaces[i].IfIndex) + interfaces[i].IfDescr = &index + } + if r.ifDescrRegex != nil { + normalizedIfDescr := r.ifDescrRegex.ReplaceAllString(*interfaces[i].IfDescr, *r.IfDescrRegexReplace) + interfaces[i].IfDescr = &normalizedIfDescr + } + } + + ifDescriptions := make(map[string]*device.Interface) // if the device has multiple interfaces with the same ifDescr, the ifDescr will be modified and the ifIndex will be attached // otherwise, the monitoring plugin will throw an error because of duplicate labels for i, origInterf := range interfaces { @@ -153,15 +174,13 @@ func addCheckInterfacePerformanceData(interfaces []device.Interface, r *monitori } else { ifDescriptions[*origInterf.IfDescr] = &interfaces[i] } - } else { - if interfaces[i].IfIndex == nil { - return errors.New("interface does not have an ifDescription and ifIndex") - } - x := fmt.Sprint(*interfaces[i].IfIndex) - interfaces[i].IfDescr = &x } } + return nil +} + +func addCheckInterfacePerformanceData(interfaces []device.Interface, r *monitoringplugin.Response) error { for _, i := range interfaces { //error_counter_in if i.IfInErrors != nil {