From 8a6661b9d551f8d61048c3de7d74b2b4f15c740f Mon Sep 17 00:00:00 2001 From: mikameyer Date: Wed, 29 Sep 2021 10:45:41 +0200 Subject: [PATCH 1/7] added groupproperty reader support for multiple index levels --- config/codecommunicator/adva.go | 8 +- config/codecommunicator/ios.go | 4 +- config/codecommunicator/junos.go | 4 +- config/codecommunicator/timos-sas.go | 4 +- config/codecommunicator/timos.go | 6 +- config/deviceclass/generic/ironware.yaml | 9 +- internal/deviceclass/groupproperty/filter.go | 12 +- .../deviceclass/groupproperty/oidReader.go | 67 +++---- internal/deviceclass/groupproperty/reader.go | 20 ++- .../deviceclass/groupproperty/reader_test.go | 164 +++++++++--------- internal/network/snmp_client.go | 46 ++++- internal/network/snmp_client_test.go | 72 ++++++++ internal/utility/utility.go | 14 +- internal/value/value.go | 2 +- 14 files changed, 268 insertions(+), 164 deletions(-) create mode 100644 internal/network/snmp_client_test.go diff --git a/config/codecommunicator/adva.go b/config/codecommunicator/adva.go index 376e40f..9b194d3 100644 --- a/config/codecommunicator/adva.go +++ b/config/codecommunicator/adva.go @@ -110,7 +110,7 @@ func (c *advaCommunicator) getDWDMInterfaces(ctx context.Context, interfaces []d } // corrected fec 15m - res, err := con.SNMP.SnmpClient.SNMPGet(ctx, network.OID(".1.3.6.1.4.1.2544.1.11.2.6.2.180.1.2.").AddSuffix(fmt.Sprint(*interf.IfIndex)+".1")) + res, err := con.SNMP.SnmpClient.SNMPGet(ctx, network.OID(".1.3.6.1.4.1.2544.1.11.2.6.2.180.1.2.").AddIndex(fmt.Sprint(*interf.IfIndex)+".1")) if err == nil && len(res) == 1 { val, err := res[0].GetValue() if err != nil { @@ -133,7 +133,7 @@ func (c *advaCommunicator) getDWDMInterfaces(ctx context.Context, interfaces []d } // uncorrected fec 15m - res, err = con.SNMP.SnmpClient.SNMPGet(ctx, network.OID(".1.3.6.1.4.1.2544.1.11.2.6.2.180.1.3.").AddSuffix(fmt.Sprint(*interf.IfIndex)+".1")) + res, err = con.SNMP.SnmpClient.SNMPGet(ctx, network.OID(".1.3.6.1.4.1.2544.1.11.2.6.2.180.1.3.").AddIndex(fmt.Sprint(*interf.IfIndex)+".1")) if err == nil && len(res) == 1 { val, err := res[0].GetValue() if err != nil { @@ -156,7 +156,7 @@ func (c *advaCommunicator) getDWDMInterfaces(ctx context.Context, interfaces []d } // corrected fec 1d - res, err = con.SNMP.SnmpClient.SNMPGet(ctx, network.OID(".1.3.6.1.4.1.2544.1.11.2.6.2.181.1.2.").AddSuffix(fmt.Sprint(*interf.IfIndex)+".1")) + res, err = con.SNMP.SnmpClient.SNMPGet(ctx, network.OID(".1.3.6.1.4.1.2544.1.11.2.6.2.181.1.2.").AddIndex(fmt.Sprint(*interf.IfIndex)+".1")) if err == nil && len(res) == 1 { val, err := res[0].GetValue() if err != nil { @@ -179,7 +179,7 @@ func (c *advaCommunicator) getDWDMInterfaces(ctx context.Context, interfaces []d } // uncorrected fec 1d - res, err = con.SNMP.SnmpClient.SNMPGet(ctx, network.OID(".1.3.6.1.4.1.2544.1.11.2.6.2.181.1.3.").AddSuffix(fmt.Sprint(*interf.IfIndex)+".1")) + res, err = con.SNMP.SnmpClient.SNMPGet(ctx, network.OID(".1.3.6.1.4.1.2544.1.11.2.6.2.181.1.3.").AddIndex(fmt.Sprint(*interf.IfIndex)+".1")) if err == nil && len(res) == 1 { valFloat, err := res[0].GetValue() if err != nil { diff --git a/config/codecommunicator/ios.go b/config/codecommunicator/ios.go index d62435e..062a61f 100644 --- a/config/codecommunicator/ios.go +++ b/config/codecommunicator/ios.go @@ -148,13 +148,13 @@ func (c *iosCommunicator) getMemoryComponentMemoryUsage(ctx context.Context, poo idx := strings.Split(poolLabelVal.GetOID().String(), poolLabelsOID.String())[1] // get used value for memory pool - used, err := c.getMemoryDecimalValue(ctx, con, usedOID.AddSuffix(idx), usedHCOID.AddSuffix(idx)) + used, err := c.getMemoryDecimalValue(ctx, con, usedOID.AddIndex(idx), usedHCOID.AddIndex(idx)) if err != nil { return nil, errors.Wrapf(err, "failed to get used value for mempool '%s'", poolLabel) } // get free value for memory pool - free, err := c.getMemoryDecimalValue(ctx, con, freeOID.AddSuffix(idx), freeHCOID.AddSuffix(idx)) + free, err := c.getMemoryDecimalValue(ctx, con, freeOID.AddIndex(idx), freeHCOID.AddIndex(idx)) if err != nil { return nil, errors.Wrapf(err, "failed to get free value for mempool '%s'", poolLabel) } diff --git a/config/codecommunicator/junos.go b/config/codecommunicator/junos.go index 6ea7c69..6554bf1 100644 --- a/config/codecommunicator/junos.go +++ b/config/codecommunicator/junos.go @@ -257,7 +257,7 @@ func (c *junosCommunicator) GetCPUComponentCPULoad(ctx context.Context) ([]devic jnxOperatingCPUOID := network.OID(".1.3.6.1.4.1.2636.3.1.13.1.8") var cpus []device.CPU for i, index := range indices { - response, err := con.SNMP.SnmpClient.SNMPGet(ctx, jnxOperatingCPUOID.AddSuffix(index.index)) + response, err := con.SNMP.SnmpClient.SNMPGet(ctx, jnxOperatingCPUOID.AddIndex(index.index)) if err != nil { return nil, errors.Wrap(err, "failed to get CPU load") } else if len(response) != 1 { @@ -422,7 +422,7 @@ func (c *junosCommunicator) GetMemoryComponentMemoryUsage(ctx context.Context) ( return nil, errors.Wrap(err, "failed to get routing engine indices") } for i, index := range indices { - response, err := con.SNMP.SnmpClient.SNMPGet(ctx, network.OID(".1.3.6.1.4.1.2636.3.1.13.1.11").AddSuffix(index.index)) + response, err := con.SNMP.SnmpClient.SNMPGet(ctx, network.OID(".1.3.6.1.4.1.2636.3.1.13.1.11").AddIndex(index.index)) if err != nil { return nil, errors.Wrap(err, "failed to get memory usage") } else if len(response) != 1 { diff --git a/config/codecommunicator/timos-sas.go b/config/codecommunicator/timos-sas.go index 7a5c0bb..e914a74 100644 --- a/config/codecommunicator/timos-sas.go +++ b/config/codecommunicator/timos-sas.go @@ -52,13 +52,13 @@ func (c *timosSASCommunicator) GetInterfaces(ctx context.Context, filter ...grou } // retrieve inbound - inbound, err := getCounterFromSnmpGet(ctx, network.OID(".1.3.6.1.4.1.6527.6.2.2.2.8.1.1.1.4.").AddSuffix(suffix[1]+"."+physIndex+"."+subID)) + inbound, err := getCounterFromSnmpGet(ctx, network.OID(".1.3.6.1.4.1.6527.6.2.2.2.8.1.1.1.4.").AddIndex(suffix[1]+"."+physIndex+"."+subID)) if err != nil { return nil, errors.Wrap(err, "failed to retrieve inbound counter") } // retrieve outbound - outbound, err := getCounterFromSnmpGet(ctx, network.OID(".1.3.6.1.4.1.6527.6.2.2.2.8.1.1.1.6.").AddSuffix(suffix[1]+"."+physIndex+"."+subID)) + outbound, err := getCounterFromSnmpGet(ctx, network.OID(".1.3.6.1.4.1.6527.6.2.2.2.8.1.1.1.6.").AddIndex(suffix[1]+"."+physIndex+"."+subID)) if err != nil { return nil, errors.Wrap(err, "failed to retrieve outbound counter") } diff --git a/config/codecommunicator/timos.go b/config/codecommunicator/timos.go index 10f3033..4d3674e 100644 --- a/config/codecommunicator/timos.go +++ b/config/codecommunicator/timos.go @@ -70,13 +70,13 @@ func (c *timosCommunicator) GetInterfaces(ctx context.Context, filter ...grouppr } // retrieve admin status - admin, err := getStatusFromSnmpGet(ctx, network.OID(".1.3.6.1.4.1.6527.3.1.2.4.3.2.1.6.").AddSuffix(suffix[1]+"."+physIndex+"."+subID)) + admin, err := getStatusFromSnmpGet(ctx, network.OID(".1.3.6.1.4.1.6527.3.1.2.4.3.2.1.6.").AddIndex(suffix[1]+"."+physIndex+"."+subID)) if err != nil { return nil, errors.Wrap(err, "failed to retrieve admin status") } // retrieve oper status - oper, err := getStatusFromSnmpGet(ctx, network.OID(".1.3.6.1.4.1.6527.3.1.2.4.3.2.1.7.").AddSuffix(suffix[1]+"."+physIndex+"."+subID)) + oper, err := getStatusFromSnmpGet(ctx, network.OID(".1.3.6.1.4.1.6527.3.1.2.4.3.2.1.7.").AddIndex(suffix[1]+"."+physIndex+"."+subID)) if err != nil { return nil, errors.Wrap(err, "failed to retrieve oper status") } @@ -114,7 +114,7 @@ func getPhysPortDescriptions(ctx context.Context) (map[string]string, error) { if err != nil { return nil, errors.Wrap(err, "couldn't get string value") } - index := strings.TrimPrefix(response.GetOID().String(), physPortsOID.AddSuffix(".").String()) + index := strings.TrimPrefix(response.GetOID().String(), physPortsOID.AddIndex(".").String()) indexDescriptions[index] = description.String() } return indexDescriptions, nil diff --git a/config/deviceclass/generic/ironware.yaml b/config/deviceclass/generic/ironware.yaml index 213dc60..b42edd8 100644 --- a/config/deviceclass/generic/ironware.yaml +++ b/config/deviceclass/generic/ironware.yaml @@ -55,4 +55,11 @@ components: mappings: 1: "unknown" 2: "normal" - 3: "critical" \ No newline at end of file + 3: "critical" + temperature: + detection: snmpwalk + values: + description: + oid: .1.3.6.1.4.1.1991.1.1.2.13.1.1.3 + temperature: + oid: .1.3.6.1.4.1.1991.1.1.2.13.1.1.4 \ No newline at end of file diff --git a/internal/deviceclass/groupproperty/filter.go b/internal/deviceclass/groupproperty/filter.go index a0ee273..1cf760e 100644 --- a/internal/deviceclass/groupproperty/filter.go +++ b/internal/deviceclass/groupproperty/filter.go @@ -6,7 +6,6 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" "regexp" - "strconv" "strings" ) @@ -85,19 +84,18 @@ func (g *groupFilter) applySNMP(ctx context.Context, reader snmpReader) (snmpRea } for index, result := range results { - indexString := strconv.Itoa(index) if regex.MatchString(result.(value.Value).String()) { // if filter matches add to filtered indices map and delete from wanted indices - reader.filteredIndices[indexString] = struct{}{} - delete(reader.wantedIndices, indexString) + reader.filteredIndices[index] = struct{}{} + delete(reader.wantedIndices, index) log.Ctx(ctx).Debug().Str("filter_key", g.key).Str("filter_regex", g.regex). Str("received_value", result.(value.Value).String()). - Msgf("filter matched on index '%d'", index) + Msgf("filter matched on index '%s'", index) } else { // if filter does not match check if index was filtered before - if _, ok := reader.filteredIndices[indexString]; !ok { + if _, ok := reader.filteredIndices[index]; !ok { // if not add it to wanted indices map - reader.wantedIndices[indexString] = struct{}{} + reader.wantedIndices[index] = struct{}{} } } } diff --git a/internal/deviceclass/groupproperty/oidReader.go b/internal/deviceclass/groupproperty/oidReader.go index de29521..7fa2601 100644 --- a/internal/deviceclass/groupproperty/oidReader.go +++ b/internal/deviceclass/groupproperty/oidReader.go @@ -11,8 +11,6 @@ import ( "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "strconv" - "strings" ) //go:generate go run github.com/vektra/mockery/v2 --name=OIDReader --inpackage @@ -50,6 +48,7 @@ func Interface2OIDReader(i interface{}) (OIDReader, error) { if ignore, ok := dataMap["ignore"]; ok { if b, ok := ignore.(bool); ok && b { + //TODO delete from map? result[valString] = &emptyOIDReader{} continue } @@ -74,14 +73,14 @@ func Interface2OIDReader(i interface{}) (OIDReader, error) { } type OIDReader interface { - readOID(context.Context, []value.Value, bool) (map[int]interface{}, error) + readOID(context.Context, []string, bool) (map[string]interface{}, error) } // deviceClassOIDs is a recursive data structure which maps labels to either a single OID (deviceClassOID) or another deviceClassOIDs type deviceClassOIDs map[string]OIDReader -func (d *deviceClassOIDs) readOID(ctx context.Context, indices []value.Value, skipEmpty bool) (map[int]interface{}, error) { - result := make(map[int]map[string]interface{}) +func (d *deviceClassOIDs) readOID(ctx context.Context, indices []string, skipEmpty bool) (map[string]interface{}, error) { + result := make(map[string]map[string]interface{}) for label, reader := range *d { res, err := reader.readOID(ctx, indices, skipEmpty) if err != nil { @@ -100,7 +99,7 @@ func (d *deviceClassOIDs) readOID(ctx context.Context, indices []value.Value, sk } } - r := make(map[int]interface{}) + r := make(map[string]interface{}) for k, v := range result { r[k] = v } @@ -136,10 +135,10 @@ type deviceClassOID struct { indicesMapping OIDReader } -func (d *deviceClassOID) readOID(ctx context.Context, indices []value.Value, skipEmpty bool) (map[int]interface{}, error) { - result := make(map[int]interface{}) +func (d *deviceClassOID) readOID(ctx context.Context, indices []string, skipEmpty bool) (map[string]interface{}, error) { + result := make(map[string]interface{}) - logger := log.Ctx(ctx).With().Str("oid", string(d.OID)).Logger() + logger := log.Ctx(ctx).With().Str("oid", d.OID.String()).Logger() ctx = logger.WithContext(ctx) con, ok := network.DeviceConnectionFromContext(ctx) @@ -160,22 +159,22 @@ func (d *deviceClassOID) readOID(ctx context.Context, indices []value.Value, ski return nil, errors.Wrap(err, "failed to read indices") } - ifIndexRelIndex := make(map[string]value.Value) - for relIndex, ifIndex := range mappingIndices { - ifIndexValue, ok := ifIndex.(value.Value) + indexRelIndex := make(map[string]string) + for relIndex, index := range mappingIndices { + indexValue, ok := index.(value.Value) if !ok { return nil, errors.New("index mapping oid didn't return a result of type 'value'") } - ifIndexString := ifIndexValue.String() - if idx, ok := ifIndexRelIndex[ifIndexString]; ok { - return nil, fmt.Errorf("index mapping resulted in duplicate ifIndex mapping on '%s'", idx.String()) + indexString := indexValue.String() + if idx, ok := indexRelIndex[indexString]; ok { + return nil, fmt.Errorf("index mapping resulted in duplicate index mapping on '%s'", idx) } - ifIndexRelIndex[ifIndexString] = value.New(relIndex) + indexRelIndex[indexString] = relIndex } - var newIndices []value.Value - for _, ifIndex := range indices { - if relIndex, ok := ifIndexRelIndex[ifIndex.String()]; ok { + var newIndices []string + for _, index := range indices { + if relIndex, ok := indexRelIndex[index]; ok { newIndices = append(newIndices, relIndex) } } @@ -183,13 +182,9 @@ func (d *deviceClassOID) readOID(ctx context.Context, indices []value.Value, ski indices = newIndices } - oid := d.OID - if !strings.HasSuffix(oid.String(), ".") { - oid = oid.AddSuffix(".") - } var oids []network.OID for _, index := range indices { - oids = append(oids, oid.AddSuffix(index.String())) + oids = append(oids, d.OID.AddIndex(index)) } snmpResponse, err = con.SNMP.SnmpClient.SNMPGet(ctx, oids...) } else { @@ -213,7 +208,7 @@ func (d *deviceClassOID) readOID(ctx context.Context, indices []value.Value, ski continue } if !res.IsEmpty() || !skipEmpty { - resNormalized, err := d.operators.Apply(ctx, value.New(res)) + resNormalized, err := d.operators.Apply(ctx, res) if err != nil { if tholaerr.IsDidNotMatchError(err) { continue @@ -221,13 +216,7 @@ func (d *deviceClassOID) readOID(ctx context.Context, indices []value.Value, ski log.Ctx(ctx).Debug().Err(err).Msgf("response couldn't be normalized (response: %s)", res) return nil, errors.Wrapf(err, "response couldn't be normalized (response: %s)", res) } - oid := strings.Split(response.GetOID().String(), ".") - index, err := strconv.Atoi(oid[len(oid)-1]) - if err != nil { - log.Ctx(ctx).Debug().Err(err).Msg("index isn't an integer") - return nil, errors.Wrap(err, "index isn't an integer") - } - result[index] = resNormalized + result[response.GetOID().GetIndexAfterOID(d.OID)] = resNormalized } } @@ -237,27 +226,23 @@ func (d *deviceClassOID) readOID(ctx context.Context, indices []value.Value, ski if err != nil { return nil, errors.Wrap(err, "failed to read mapping indices") } - mappedResult := make(map[int]interface{}) + mappedResult := make(map[string]interface{}) for k, v := range result { mappedIdx, ok := mappingIndices[k] if !ok { continue } - idxValue, ok := mappedIdx.(value.Value) + idx, ok := mappedIdx.(value.Value) if !ok { return nil, errors.New("index mapping oid didn't return a result of type 'value'") } - idx, err := idxValue.Int() - if err != nil { - return nil, errors.Wrap(err, "failed to convert Value to int") - } - if _, ok := mappedResult[idx]; ok { + if _, ok := mappedResult[idx.String()]; ok { return nil, fmt.Errorf("index mapping resulted in duplicate index '%d'", idx) } - mappedResult[idx] = v + mappedResult[idx.String()] = v } result = mappedResult } @@ -266,7 +251,7 @@ func (d *deviceClassOID) readOID(ctx context.Context, indices []value.Value, ski type emptyOIDReader struct{} -func (n *emptyOIDReader) readOID(context.Context, []value.Value, bool) (map[int]interface{}, error) { +func (n *emptyOIDReader) readOID(context.Context, []string, bool) (map[string]interface{}, error) { return nil, tholaerr.NewComponentNotFoundError("oid is ignored") } diff --git a/internal/deviceclass/groupproperty/reader.go b/internal/deviceclass/groupproperty/reader.go index 15649b6..6ea1208 100644 --- a/internal/deviceclass/groupproperty/reader.go +++ b/internal/deviceclass/groupproperty/reader.go @@ -8,7 +8,6 @@ import ( "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "strconv" ) func Interface2Reader(i interface{}, parentReader Reader) (Reader, error) { @@ -142,7 +141,7 @@ type snmpReader struct { } func (s snmpReader) getProperty(ctx context.Context) (PropertyGroups, []value.Value, error) { - var wantedIndices []value.Value + var wantedIndices []string useSNMPGetsInsteadOfWalk, ok := network.SNMPGetsInsteadOfWalkFromContext(ctx) if !ok { @@ -162,7 +161,7 @@ func (s snmpReader) getProperty(ctx context.Context) (PropertyGroups, []value.Va } for index := range indices { - wantedIndices = append(wantedIndices, value.New(index)) + wantedIndices = append(wantedIndices, index) } } @@ -178,25 +177,30 @@ func (s snmpReader) getProperty(ctx context.Context) (PropertyGroups, []value.Va //TODO efficiency size := len(groups) for i := 0; i < size; i++ { - var smallestIndex int + var smallestIndex string firstRun := true for index := range groups { if firstRun { smallestIndex = index firstRun = false + continue + } + cmp, err := network.OID(index).Cmp(network.OID(smallestIndex)) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to compare indices") } - if index < smallestIndex { + if cmp == -1 { smallestIndex = index } } x, ok := groups[smallestIndex].(map[string]interface{}) if !ok { - return nil, nil, fmt.Errorf("oidReader for index '%d' returned unexpected data type: %T", smallestIndex, groups[smallestIndex]) + return nil, nil, fmt.Errorf("oidReader for index '%s' returned unexpected data type: %T", smallestIndex, groups[smallestIndex]) } delete(groups, smallestIndex) if !useSNMPGetsInsteadOfWalk { - if _, ok := s.filteredIndices[strconv.Itoa(smallestIndex)]; ok { + if _, ok := s.filteredIndices[smallestIndex]; ok { continue } } @@ -222,7 +226,7 @@ func (s snmpReader) getIndices(ctx context.Context) (map[string]struct{}, error) } res := make(map[string]struct{}) for index := range indices { - res[strconv.Itoa(index)] = struct{}{} + res[index] = struct{}{} } return res, nil diff --git a/internal/deviceclass/groupproperty/reader_test.go b/internal/deviceclass/groupproperty/reader_test.go index 4cba3f4..c1ba064 100644 --- a/internal/deviceclass/groupproperty/reader_test.go +++ b/internal/deviceclass/groupproperty/reader_test.go @@ -35,11 +35,11 @@ func TestDeviceClassOID_readOID(t *testing.T) { }, } - expected := map[int]interface{}{ - 1: value.New("Port 1"), - 2: value.New("Port 2"), - 3: value.New("Port 3"), - 4: value.New(""), + expected := map[string]interface{}{ + "1": value.New("Port 1"), + "2": value.New("Port 2"), + "3": value.New("Port 3"), + "4": value.New(""), } res, err := sut.readOID(ctx, nil, false) @@ -72,10 +72,10 @@ func TestDeviceClassOID_readOID_skipEmpty(t *testing.T) { }, } - expected := map[int]interface{}{ - 1: value.New("Port 1"), - 2: value.New("Port 2"), - 3: value.New("Port 3"), + expected := map[string]interface{}{ + "1": value.New("Port 1"), + "2": value.New("Port 2"), + "3": value.New("Port 3"), } res, err := sut.readOID(ctx, nil, true) @@ -110,14 +110,14 @@ func TestDeviceClassOID_readOID_withIndices(t *testing.T) { }, } - expected := map[int]interface{}{ - 1: value.New("Port 1"), - 2: value.New("Port 2"), - 3: value.New("Port 3"), - 4: value.New(""), + expected := map[string]interface{}{ + "1": value.New("Port 1"), + "2": value.New("Port 2"), + "3": value.New("Port 3"), + "4": value.New(""), } - res, err := sut.readOID(ctx, []value.Value{value.New(1), value.New(2), value.New(3), value.New(4)}, false) + res, err := sut.readOID(ctx, []string{"1", "2", "3", "4"}, false) if assert.NoError(t, err) { assert.Equal(t, expected, res) } @@ -149,13 +149,13 @@ func TestDeviceClassOID_readOID_withIndicesSkipEmpty(t *testing.T) { }, } - expected := map[int]interface{}{ - 1: value.New("Port 1"), - 2: value.New("Port 2"), - 3: value.New("Port 3"), + expected := map[string]interface{}{ + "1": value.New("Port 1"), + "2": value.New("Port 2"), + "3": value.New("Port 3"), } - res, err := sut.readOID(ctx, []value.Value{value.New(1), value.New(2), value.New(3), value.New(4)}, true) + res, err := sut.readOID(ctx, []string{"1", "2", "3", "4"}, true) if assert.NoError(t, err) { assert.Equal(t, expected, res) } @@ -198,10 +198,10 @@ func TestDeviceClassOID_readOID_indicesMapping(t *testing.T) { indicesMapping: &indicesMappingDeviceClassOid, } - expected := map[int]interface{}{ - 1: value.New("Port 3"), - 2: value.New("Port 2"), - 3: value.New("Port 1"), + expected := map[string]interface{}{ + "1": value.New("Port 3"), + "2": value.New("Port 2"), + "3": value.New("Port 1"), } res, err := sut.readOID(ctx, nil, false) @@ -246,13 +246,13 @@ func TestDeviceClassOID_readOID_indicesMappingWithIndices(t *testing.T) { indicesMapping: &indicesMappingDeviceClassOid, } - expected := map[int]interface{}{ - 1: value.New("Port 3"), - 2: value.New("Port 2"), - 3: value.New("Port 1"), + expected := map[string]interface{}{ + "1": value.New("Port 3"), + "2": value.New("Port 2"), + "3": value.New("Port 1"), } - res, err := sut.readOID(ctx, []value.Value{value.New(1), value.New(2), value.New(3), value.New(4)}, false) + res, err := sut.readOID(ctx, []string{"1", "2", "3", "4"}, false) if assert.NoError(t, err) { assert.Equal(t, expected, res) } @@ -265,18 +265,18 @@ func TestDeviceClassOIDs_readOID(t *testing.T) { ctx := context.Background() ifIndexOidReader. - On("readOID", ctx, []value.Value(nil), true). - Return(map[int]interface{}{ - 1: value.New(1), - 2: value.New(2), - 3: value.New(3), + On("readOID", ctx, []string(nil), true). + Return(map[string]interface{}{ + "1": value.New(1), + "2": value.New(2), + "3": value.New(3), }, nil) ifDescrOidReader. - On("readOID", ctx, []value.Value(nil), true). - Return(map[int]interface{}{ - 1: value.New("Port 1"), - 2: value.New("Port 2"), - 3: value.New("Port 3"), + On("readOID", ctx, []string(nil), true). + Return(map[string]interface{}{ + "1": value.New("Port 1"), + "2": value.New("Port 2"), + "3": value.New("Port 3"), }, nil) sut := deviceClassOIDs{ @@ -284,22 +284,22 @@ func TestDeviceClassOIDs_readOID(t *testing.T) { "ifDescr": &ifDescrOidReader, } - expected := map[int]interface{}{ - 1: map[string]interface{}{ + expected := map[string]interface{}{ + "1": map[string]interface{}{ "ifIndex": value.New(1), "ifDescr": value.New("Port 1"), }, - 2: map[string]interface{}{ + "2": map[string]interface{}{ "ifIndex": value.New(2), "ifDescr": value.New("Port 2"), }, - 3: map[string]interface{}{ + "3": map[string]interface{}{ "ifIndex": value.New(3), "ifDescr": value.New("Port 3"), }, } - res, err := sut.readOID(ctx, []value.Value(nil), true) + res, err := sut.readOID(ctx, []string(nil), true) if assert.NoError(t, err) { assert.Equal(t, expected, res) } @@ -313,27 +313,27 @@ func TestDeviceClassOIDs_readOID_multipleLevel(t *testing.T) { ctx := context.Background() ifIndexOidReader. - On("readOID", ctx, []value.Value(nil), true). - Return(map[int]interface{}{ - 1: value.New("1"), - 2: value.New("2"), - 3: value.New("3"), + On("readOID", ctx, []string(nil), true). + Return(map[string]interface{}{ + "1": value.New("1"), + "2": value.New("2"), + "3": value.New("3"), }, nil) ifDescrOidReader. - On("readOID", ctx, []value.Value(nil), true). - Return(map[int]interface{}{ - 1: value.New("Port 1"), - 2: value.New("Port 2"), - 3: value.New("Port 3"), + On("readOID", ctx, []string(nil), true). + Return(map[string]interface{}{ + "1": value.New("Port 1"), + "2": value.New("Port 2"), + "3": value.New("Port 3"), }, nil) radioInterfaceOidReader. - On("readOID", ctx, []value.Value(nil), true). - Return(map[int]interface{}{ - 1: map[string]interface{}{ + On("readOID", ctx, []string(nil), true). + Return(map[string]interface{}{ + "1": map[string]interface{}{ "level_in": value.New(1), "level_out": value.New(-1), }, - 2: map[string]interface{}{ + "2": map[string]interface{}{ "level_in": value.New(2), "level_out": value.New(-2), }, @@ -345,8 +345,8 @@ func TestDeviceClassOIDs_readOID_multipleLevel(t *testing.T) { "radio": &radioInterfaceOidReader, } - expected := map[int]interface{}{ - 1: map[string]interface{}{ + expected := map[string]interface{}{ + "1": map[string]interface{}{ "ifIndex": value.New(1), "ifDescr": value.New("Port 1"), "radio": map[string]interface{}{ @@ -354,7 +354,7 @@ func TestDeviceClassOIDs_readOID_multipleLevel(t *testing.T) { "level_out": value.New(-1), }, }, - 2: map[string]interface{}{ + "2": map[string]interface{}{ "ifIndex": value.New(2), "ifDescr": value.New("Port 2"), "radio": map[string]interface{}{ @@ -362,13 +362,13 @@ func TestDeviceClassOIDs_readOID_multipleLevel(t *testing.T) { "level_out": value.New(-2), }, }, - 3: map[string]interface{}{ + "3": map[string]interface{}{ "ifIndex": value.New(3), "ifDescr": value.New("Port 3"), }, } - res, err := sut.readOID(ctx, []value.Value(nil), true) + res, err := sut.readOID(ctx, []string(nil), true) if assert.NoError(t, err) { assert.Equal(t, expected, res) } @@ -379,17 +379,17 @@ func TestSNMPReader_getProperty(t *testing.T) { ctx := context.Background() oidReader. - On("readOID", ctx, []value.Value(nil), true). - Return(map[int]interface{}{ - 1: map[string]interface{}{ + On("readOID", ctx, []string(nil), true). + Return(map[string]interface{}{ + "1": map[string]interface{}{ "ifIndex": value.New(1), "ifDescr": value.New("Port 1"), }, - 2: map[string]interface{}{ + "2": map[string]interface{}{ "ifIndex": value.New(2), "ifDescr": value.New("Port 2"), }, - 3: map[string]interface{}{ + "3": map[string]interface{}{ "ifIndex": value.New(3), "ifDescr": value.New("Port 3"), }, @@ -498,38 +498,38 @@ func TestSNMPReader_getProperty_getsInsteadOfWalk(t *testing.T) { ctx := network.NewContextWithSNMPGetsInsteadOfWalk(context.Background(), true) oidReader. - On("readOID", ctx, mock.MatchedBy(func(input []value.Value) bool { - return utility.SameValueSlice(input, []value.Value{ - value.New(1), - value.New(2), - value.New(3), + On("readOID", ctx, mock.MatchedBy(func(input []string) bool { + return utility.SameStringSlice(input, []string{ + "1", + "2", + "3", }) }), true). - Return(map[int]interface{}{ - 1: map[string]interface{}{ + Return(map[string]interface{}{ + "1": map[string]interface{}{ "ifIndex": value.New(1), "ifDescr": value.New("Port 1"), }, - 2: map[string]interface{}{ + "2": map[string]interface{}{ "ifIndex": value.New(2), "ifDescr": value.New("Port 2"), }, - 3: map[string]interface{}{ + "3": map[string]interface{}{ "ifIndex": value.New(3), "ifDescr": value.New("Port 3"), }, }, nil) indexOIDReader. - On("readOID", ctx, []value.Value(nil), false). - Return(map[int]interface{}{ - 1: map[string]interface{}{ + On("readOID", ctx, []string(nil), false). + Return(map[string]interface{}{ + "1": map[string]interface{}{ "ifIndex": value.New(1), }, - 2: map[string]interface{}{ + "2": map[string]interface{}{ "ifIndex": value.New(2), }, - 3: map[string]interface{}{ + "3": map[string]interface{}{ "ifIndex": value.New(3), }, }, nil) diff --git a/internal/network/snmp_client.go b/internal/network/snmp_client.go index f11f158..f16bc71 100644 --- a/internal/network/snmp_client.go +++ b/internal/network/snmp_client.go @@ -13,6 +13,7 @@ import ( "github.com/rs/zerolog/log" "golang.org/x/text/encoding/charmap" "regexp" + "strconv" "strings" "time" "unicode" @@ -843,11 +844,50 @@ func (o OID) Validate() error { // GetIndex returns the last index of the OID. func (o OID) GetIndex() string { - x := strings.Split(o.String(), ".") + x := strings.Split(strings.Trim(o.String(), "."), ".") return x[len(x)-1] } -// AddSuffix returns a OID with the specified suffix attached. -func (o OID) AddSuffix(suffix string) OID { +// GetIndexAfterOID returns the index of the OID based on the baseOID. +func (o OID) GetIndexAfterOID(baseOID OID) string { + return strings.Trim(strings.TrimPrefix(strings.TrimPrefix(o.String(), "."), strings.TrimPrefix(baseOID.String(), ".")), ".") +} + +// Cmp compares two OIDs. +func (o OID) Cmp(oid OID) (int, error) { + o1 := strings.Split(strings.Trim(o.String(), "."), ".") + o2 := strings.Split(strings.Trim(oid.String(), "."), ".") + + for i, v1 := range o1 { + if i >= len(o2) { + return 1, nil + } + v1Int, err := strconv.Atoi(v1) + if err != nil { + return 0, errors.Wrap(err, "receiver oid is not a valid oid") + } + v2Int, err := strconv.Atoi(o2[i]) + if err != nil { + return 0, errors.Wrap(err, "input oid is not a valid oid") + } + if v1Int < v2Int { + return -1, nil + } else if v1Int > v2Int { + return 1, nil + } + } + if len(o1) < len(o2) { + return -1, nil + } + return 0, nil +} + +// AddIndex returns a OID with the specified suffix OID attached. +func (o OID) AddIndex(suffix string) OID { + if !strings.HasSuffix(o.String(), ".") && !strings.HasPrefix(suffix, ".") { + return OID(o.String() + "." + suffix) + } else if strings.HasSuffix(o.String(), ".") && strings.HasPrefix(suffix, ".") { + return OID(strings.TrimSuffix(o.String(), ".") + suffix) + } return OID(o.String() + suffix) } diff --git a/internal/network/snmp_client_test.go b/internal/network/snmp_client_test.go new file mode 100644 index 0000000..3ad87c1 --- /dev/null +++ b/internal/network/snmp_client_test.go @@ -0,0 +1,72 @@ +package network + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestOID_Cmp_smaller(t *testing.T) { + res, err := OID("1.1").Cmp("1.2") + assert.NoError(t, err) + assert.Equal(t, -1, res) +} + +func TestOID_Cmp_smaller_dot(t *testing.T) { + res, err := OID(".1.1").Cmp("1.2") + assert.NoError(t, err) + assert.Equal(t, -1, res) +} + +func TestOID_Cmp_smaller_big(t *testing.T) { + res, err := OID("1.2").Cmp("1.11") + assert.NoError(t, err) + assert.Equal(t, -1, res) +} + +func TestOID_Cmp_bigger(t *testing.T) { + res, err := OID("1.2").Cmp("1.1") + assert.NoError(t, err) + assert.Equal(t, 1, res) +} + +func TestOID_Cmp_bigger_dot(t *testing.T) { + res, err := OID("1.2").Cmp(".1.1") + assert.NoError(t, err) + assert.Equal(t, 1, res) +} + +func TestOID_Cmp_bigger_big(t *testing.T) { + res, err := OID("1.244").Cmp("1.101") + assert.NoError(t, err) + assert.Equal(t, 1, res) +} + +func TestOID_Cmp_equals(t *testing.T) { + res, err := OID("1.1").Cmp("1.1") + assert.NoError(t, err) + assert.Equal(t, 0, res) +} + +func TestOID_Cmp_shorterOID(t *testing.T) { + res, err := OID("1").Cmp("1.1") + assert.NoError(t, err) + assert.Equal(t, -1, res) +} + +func TestOID_Cmp_longerOID(t *testing.T) { + res, err := OID("1.1").Cmp("1") + assert.NoError(t, err) + assert.Equal(t, 1, res) +} + +func TestOID_AddIndex(t *testing.T) { + assert.Equal(t, OID("1.1"), OID("1.").AddIndex("1")) +} + +func TestOID_AddIndex_noDot(t *testing.T) { + assert.Equal(t, OID("1.1"), OID("1").AddIndex("1")) +} + +func TestOID_AddIndex_doubleDot(t *testing.T) { + assert.Equal(t, OID("1.1"), OID("1.").AddIndex(".1")) +} diff --git a/internal/utility/utility.go b/internal/utility/utility.go index 84de5de..35cec55 100644 --- a/internal/utility/utility.go +++ b/internal/utility/utility.go @@ -1,7 +1,5 @@ package utility -import "github.com/inexio/thola/internal/value" - // IfThenElse is a wrapper for the if condition. func IfThenElse(condition bool, t interface{}, e interface{}) interface{} { if condition { @@ -73,7 +71,7 @@ func StringSliceContains(s []string, v string) bool { return false } -func SameValueSlice(x, y []value.Value) bool { +func SameStringSlice(x, y []string) bool { if len(x) != len(y) { return false } @@ -81,16 +79,16 @@ func SameValueSlice(x, y []value.Value) bool { diff := make(map[string]int, len(x)) for _, _x := range x { // 0 value for int is 0, so just increment a counter for the string - diff[_x.String()]++ + diff[_x]++ } for _, _y := range y { // If the string _y is not in diff bail out early - if _, ok := diff[_y.String()]; !ok { + if _, ok := diff[_y]; !ok { return false } - diff[_y.String()] -= 1 - if diff[_y.String()] == 0 { - delete(diff, _y.String()) + diff[_y] -= 1 + if diff[_y] == 0 { + delete(diff, _y) } } return len(diff) == 0 diff --git a/internal/value/value.go b/internal/value/value.go index 70d51ef..fc237c2 100644 --- a/internal/value/value.go +++ b/internal/value/value.go @@ -38,7 +38,7 @@ func New(i interface{}) Value { default: v = value(fmt.Sprint(t)) } - return &v + return v } // String returns the value as a string From 37e1a7b855e9f49f114e7c2d31a8bee17f7994ac Mon Sep 17 00:00:00 2001 From: mikameyer Date: Wed, 29 Sep 2021 10:46:18 +0200 Subject: [PATCH 2/7] fix typo --- internal/network/snmp_client.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/network/snmp_client.go b/internal/network/snmp_client.go index f16bc71..ecd7dde 100644 --- a/internal/network/snmp_client.go +++ b/internal/network/snmp_client.go @@ -882,12 +882,12 @@ func (o OID) Cmp(oid OID) (int, error) { return 0, nil } -// AddIndex returns a OID with the specified suffix OID attached. -func (o OID) AddIndex(suffix string) OID { - if !strings.HasSuffix(o.String(), ".") && !strings.HasPrefix(suffix, ".") { - return OID(o.String() + "." + suffix) - } else if strings.HasSuffix(o.String(), ".") && strings.HasPrefix(suffix, ".") { - return OID(strings.TrimSuffix(o.String(), ".") + suffix) - } - return OID(o.String() + suffix) +// AddIndex returns a OID with the specified index attached. +func (o OID) AddIndex(index string) OID { + if !strings.HasSuffix(o.String(), ".") && !strings.HasPrefix(index, ".") { + return OID(o.String() + "." + index) + } else if strings.HasSuffix(o.String(), ".") && strings.HasPrefix(index, ".") { + return OID(strings.TrimSuffix(o.String(), ".") + index) + } + return OID(o.String() + index) } From 335aa02c231e896ba6bdd230aeea4fc129041452 Mon Sep 17 00:00:00 2001 From: babos77 Date: Wed, 29 Sep 2021 13:17:54 +0200 Subject: [PATCH 3/7] read hardware-health fortigate --- config/codecommunicator/code_communicator.go | 2 + config/codecommunicator/fortigate.go | 218 ++++++++++++++++++ config/deviceclass/generic/fortigate.yaml | 1 + .../network_device_communicator.go | 2 +- .../deviceclass/groupproperty/oidReader.go | 6 +- internal/network/snmp_client.go | 10 +- 6 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 config/codecommunicator/fortigate.go diff --git a/config/codecommunicator/code_communicator.go b/config/codecommunicator/code_communicator.go index a73aaa9..a1310af 100644 --- a/config/codecommunicator/code_communicator.go +++ b/config/codecommunicator/code_communicator.go @@ -49,6 +49,8 @@ func GetCodeCommunicator(deviceClass communicator.Communicator, parentNetworkDev return &junosCommunicator{base}, nil case "aviat": return &aviatCommunicator{base}, nil + case "fortigate": + return &fortigateCommunicator{base}, nil } return nil, tholaerr.NewNotFoundError(fmt.Sprintf("no code communicator found for device class identifier '%s'", classIdentifier)) } diff --git a/config/codecommunicator/fortigate.go b/config/codecommunicator/fortigate.go new file mode 100644 index 0000000..2efa199 --- /dev/null +++ b/config/codecommunicator/fortigate.go @@ -0,0 +1,218 @@ +package codecommunicator + +import ( + "context" + "github.com/inexio/thola/internal/device" + "github.com/inexio/thola/internal/network" + "github.com/inexio/thola/internal/value" + "github.com/pkg/errors" + "regexp" +) + +type fortigateCommunicator struct { + codeCommunicator +} + +type fortigateSensorData struct { + Name *string + Value value.Value + State *device.HardwareHealthComponentState +} + +func (c *fortigateCommunicator) GetHardwareHealthComponentFans(ctx context.Context) ([]device.HardwareHealthComponentFan, error) { + regex, err := regexp.Compile(`Fan\s`) + if err != nil { + return nil, errors.New("invalid regular expression") + } + + sensors, err := c.getHardwareHealthComponentReadOutSensors(ctx, regex) + if err != nil { + return nil, errors.Wrap(err, "failed to read out fan sensors") + } + + var fans []device.HardwareHealthComponentFan + for _, sensor := range sensors { + var fan device.HardwareHealthComponentFan + if sensor.Name != nil { + fan.Description = sensor.Name + } + if sensor.State != nil { + fan.State = sensor.State + } + fans = append(fans, fan) + } + + return fans, nil +} + +func (c *fortigateCommunicator) GetHardwareHealthComponentTemperature(ctx context.Context) ([]device.HardwareHealthComponentTemperature, error) { + regex, err := regexp.Compile(`Temp|LM75|((TD|TR)\d+)|(DTS\d+)`) + if err != nil { + return nil, errors.New("invalid regular expression") + } + + sensors, err := c.getHardwareHealthComponentReadOutSensors(ctx, regex) + if err != nil { + return nil, errors.Wrap(err, "failed to read out fan sensors") + } + + var temps []device.HardwareHealthComponentTemperature + for _, sensor := range sensors { + var temp device.HardwareHealthComponentTemperature + if sensor.Name != nil { + temp.Description = sensor.Name + } + if sensor.State != nil { + temp.State = sensor.State + } + if sensor.Value != nil { + fl, err := sensor.Value.Float64() + if err != nil { + return nil, errors.Wrap(err, "failed to parse temperature as float64") + } + temp.Temperature = &fl + } + + temps = append(temps, temp) + } + + return temps, nil +} + +func (c *fortigateCommunicator) GetHardwareHealthComponentVoltage(ctx context.Context) ([]device.HardwareHealthComponentVoltage, error) { + regex, err := regexp.Compile(`(VOUT)|(VIN)|(VCC)|(P\d+V\d+)|(_\d+V\d+_)|(DDR)|(VCORE)|(DVDD)`) + if err != nil { + return nil, errors.New("invalid regular expression") + } + sensors, err := c.getHardwareHealthComponentReadOutSensors(ctx, regex) + if err != nil { + return nil, errors.Wrap(err, "failed to read out voltage sensors") + } + + var voltage []device.HardwareHealthComponentVoltage + for _, sensor := range sensors { + var vol device.HardwareHealthComponentVoltage + if sensor.Name != nil { + vol.Description = sensor.Name + } + if sensor.State != nil { + vol.State = sensor.State + } + if sensor.Value != nil { + fl, err := sensor.Value.Float64() + if err != nil { + return nil, errors.Wrap(err, "failed to parse voltage as float64") + } + vol.Voltage = &fl + } + + voltage = append(voltage, vol) + } + + return voltage, nil +} + +func (c *fortigateCommunicator) GetHardwareHealthComponentPowerSupply(ctx context.Context) ([]device.HardwareHealthComponentPowerSupply, error) { + regex, err := regexp.Compile(`PS.*Status`) + if err != nil { + return nil, errors.New("invalid regular expression") + } + + sensors, err := c.getHardwareHealthComponentReadOutSensors(ctx, regex) + if err != nil { + return nil, errors.Wrap(err, "failed to read out power supply sensors") + } + + var powerSupply []device.HardwareHealthComponentPowerSupply + for _, sensor := range sensors { + var ps device.HardwareHealthComponentPowerSupply + if sensor.Name != nil { + ps.Description = sensor.Name + } + if sensor.State != nil { + ps.State = sensor.State + } + + powerSupply = append(powerSupply, ps) + } + + return powerSupply, nil +} + +func (c *fortigateCommunicator) getHardwareHealthComponentReadOutSensors(ctx context.Context, regex *regexp.Regexp) ([]fortigateSensorData, error) { + con, ok := network.DeviceConnectionFromContext(ctx) + if !ok || con.SNMP == nil { + return nil, errors.New("no device connection available") + } + + var sensors []fortigateSensorData + + sensorNameOID := network.OID("1.3.6.1.4.1.12356.101.4.3.2.1.2") + valueOID := network.OID("1.3.6.1.4.1.12356.101.4.3.2.1.3") + alarmOID := network.OID("1.3.6.1.4.1.12356.101.4.3.2.1.4") + + sensorResults, err := con.SNMP.SnmpClient.SNMPWalk(ctx, sensorNameOID) + if err != nil { + return nil, errors.Wrap(err, "failed to read out sensors oid") + } + + for _, sensorResult := range sensorResults { + v, err := sensorResult.GetValue() + if err != nil { + return nil, errors.Wrap(err, "failed to get value of oid response") + } + + sensorName := v.String() + if regex != nil && !regex.MatchString(sensorName) { + continue + } + + var sensor fortigateSensorData + sensor.Name = &sensorName + + oid := sensorResult.GetOID() + subTree, err := oid.GetIndexAfterOID(sensorNameOID) + if err != nil { + return nil, errors.Wrapf(err, "failed to get subtree of oid %s", sensorResult.GetOID()) + } + + // get value + resValue, err := con.SNMP.SnmpClient.SNMPGet(ctx, valueOID.AddIndex(subTree)) + if err != nil { + return nil, errors.Wrapf(err, "failed to read out value of sensor %s", sensorName) + } + if len(resValue) > 0 { + v, err := resValue[0].GetValue() + if err != nil { + return nil, errors.Wrapf(err, "failed to get value for sensor %s", sensorName) + } + sensor.Value = v + } + + // get alarm + resAlarm, err := con.SNMP.SnmpClient.SNMPGet(ctx, alarmOID.AddIndex(subTree)) + // alarm is not mandatory and does not exist for every sensor + if err == nil && len(resAlarm) > 0 { + alarm, err := resAlarm[0].GetValue() + if err != nil { + return nil, errors.Wrapf(err, "failed to get alarm for sensor %s", sensorName) + } + + switch alarm.String() { + case "0": + state := device.HardwareHealthComponentState("normal") + sensor.State = &state + case "1": + state := device.HardwareHealthComponentState("critical") + sensor.State = &state + default: + state := device.HardwareHealthComponentState("unknown") + sensor.State = &state + } + } + + sensors = append(sensors, sensor) + } + + return sensors, nil +} diff --git a/config/deviceclass/generic/fortigate.yaml b/config/deviceclass/generic/fortigate.yaml index dfdfbdf..215b37d 100644 --- a/config/deviceclass/generic/fortigate.yaml +++ b/config/deviceclass/generic/fortigate.yaml @@ -4,6 +4,7 @@ config: components: cpu: true memory: true + hardware_health: true match: conditions: diff --git a/internal/communicator/network_device_communicator.go b/internal/communicator/network_device_communicator.go index 50c2666..3499eb8 100644 --- a/internal/communicator/network_device_communicator.go +++ b/internal/communicator/network_device_communicator.go @@ -454,7 +454,7 @@ func (c *networkDeviceCommunicator) GetHardwareHealthComponent(ctx context.Conte } if empty { - return device.HardwareHealthComponent{}, tholaerr.NewNotFoundError("no sbc data available") + return device.HardwareHealthComponent{}, tholaerr.NewNotFoundError("no hardware health data available") } return hardwareHealth, nil diff --git a/internal/deviceclass/groupproperty/oidReader.go b/internal/deviceclass/groupproperty/oidReader.go index 7fa2601..9c4ff6d 100644 --- a/internal/deviceclass/groupproperty/oidReader.go +++ b/internal/deviceclass/groupproperty/oidReader.go @@ -216,7 +216,11 @@ func (d *deviceClassOID) readOID(ctx context.Context, indices []string, skipEmpt log.Ctx(ctx).Debug().Err(err).Msgf("response couldn't be normalized (response: %s)", res) return nil, errors.Wrapf(err, "response couldn't be normalized (response: %s)", res) } - result[response.GetOID().GetIndexAfterOID(d.OID)] = resNormalized + idx, err := response.GetOID().GetIndexAfterOID(d.OID) + if err != nil { + return nil, errors.Wrap(err, "failed to get index after oid") + } + result[idx] = resNormalized } } diff --git a/internal/network/snmp_client.go b/internal/network/snmp_client.go index ecd7dde..b97ccb2 100644 --- a/internal/network/snmp_client.go +++ b/internal/network/snmp_client.go @@ -849,8 +849,14 @@ func (o OID) GetIndex() string { } // GetIndexAfterOID returns the index of the OID based on the baseOID. -func (o OID) GetIndexAfterOID(baseOID OID) string { - return strings.Trim(strings.TrimPrefix(strings.TrimPrefix(o.String(), "."), strings.TrimPrefix(baseOID.String(), ".")), ".") +func (o OID) GetIndexAfterOID(baseOID OID) (string, error) { + oidStr := strings.TrimPrefix(o.String(), ".") + baseOIDStr := strings.TrimPrefix(baseOID.String(), ".") + + if !strings.HasPrefix(oidStr, baseOIDStr) { + return "", errors.New("subtree does not exist") + } + return strings.Trim(strings.TrimPrefix(oidStr, baseOIDStr), "."), nil } // Cmp compares two OIDs. From 319c1f0835ecb2b1aeda7a18ad527f3c1f1af5ea Mon Sep 17 00:00:00 2001 From: mikameyer Date: Wed, 29 Sep 2021 16:00:37 +0200 Subject: [PATCH 4/7] added chassis power supply to cisco hardware health --- config/codecommunicator/adva.go | 4 +- .../ekinops_module_reader_opm.go | 3 +- config/codecommunicator/fortigate.go | 6 +- config/codecommunicator/ios.go | 85 ++++++++++++++++++- config/codecommunicator/junos.go | 6 +- internal/device/device.go | 61 +++++++------ ...check_interface_metrics_request_process.go | 24 +++--- internal/request/check_sbc_request_process.go | 34 +++++--- 8 files changed, 164 insertions(+), 59 deletions(-) diff --git a/config/codecommunicator/adva.go b/config/codecommunicator/adva.go index 9b194d3..7e221a6 100644 --- a/config/codecommunicator/adva.go +++ b/config/codecommunicator/adva.go @@ -250,7 +250,7 @@ func (c *advaCommunicator) getChannels(ctx context.Context, interfaces []device. valFin, _ := a.Mul(b).Float64() channels[subtree] = device.OpticalChannel{ - Channel: s[len(s)-2], + Channel: &s[len(s)-2], RXPower: &valFin, } } @@ -278,7 +278,7 @@ func (c *advaCommunicator) getChannels(ctx context.Context, interfaces []device. if channel, ok := channels[subtree]; !ok { channels[subtree] = device.OpticalChannel{ - Channel: s[len(s)-2], + Channel: &s[len(s)-2], TXPower: &valFin, } } else { diff --git a/config/codecommunicator/ekinops_module_reader_opm.go b/config/codecommunicator/ekinops_module_reader_opm.go index 692e4c0..888acf7 100644 --- a/config/codecommunicator/ekinops_module_reader_opm.go +++ b/config/codecommunicator/ekinops_module_reader_opm.go @@ -165,10 +165,11 @@ func ekinopsReadOPMMetrics(ctx context.Context, oids ekinopsOPMOIDs) ([]device.O for k := range opticalOPMInterfaces { channelNum := 13.0 for channelIdx := 16; channelIdx <= 776; channelIdx += 8 { + channelName := fmt.Sprintf("C%.2f", channelNum) rxPower := channelValues[k][channelIdx] channel := device.OpticalChannel{ - Channel: fmt.Sprintf("C%.2f", channelNum), + Channel: &channelName, RXPower: &rxPower, } diff --git a/config/codecommunicator/fortigate.go b/config/codecommunicator/fortigate.go index 2efa199..47764a0 100644 --- a/config/codecommunicator/fortigate.go +++ b/config/codecommunicator/fortigate.go @@ -200,13 +200,13 @@ func (c *fortigateCommunicator) getHardwareHealthComponentReadOutSensors(ctx con switch alarm.String() { case "0": - state := device.HardwareHealthComponentState("normal") + state := device.HardwareHealthComponentStateNormal sensor.State = &state case "1": - state := device.HardwareHealthComponentState("critical") + state := device.HardwareHealthComponentStateCritical sensor.State = &state default: - state := device.HardwareHealthComponentState("unknown") + state := device.HardwareHealthComponentStateUnknown sensor.State = &state } } diff --git a/config/codecommunicator/ios.go b/config/codecommunicator/ios.go index 062a61f..6dad1b6 100644 --- a/config/codecommunicator/ios.go +++ b/config/codecommunicator/ios.go @@ -7,6 +7,7 @@ import ( "github.com/inexio/thola/internal/network" "github.com/pkg/errors" "github.com/shopspring/decimal" + "strconv" "strings" ) @@ -116,12 +117,22 @@ func (c *iosCommunicator) getCPUBySNMPResponse(res network.SNMPResponse) (device // GetMemoryComponentMemoryUsage returns the memory usage of ios devices. func (c *iosCommunicator) GetMemoryComponentMemoryUsage(ctx context.Context) ([]device.MemoryPool, error) { // first try cisco enhanced mempool mib, if it fails try old mempool mib - pools, err := c.getMemoryComponentMemoryUsage(ctx, ".1.3.6.1.4.1.9.9.221.1.1.1.1.3", ".1.3.6.1.4.1.9.9.221.1.1.1.1.7", ".1.3.6.1.4.1.9.9.221.1.1.1.1.18", ".1.3.6.1.4.1.9.9.221.1.1.1.1.8", ".1.3.6.1.4.1.9.9.221.1.1.1.1.20") + pools, err := c.getMemoryComponentMemoryUsage(ctx, + ".1.3.6.1.4.1.9.9.221.1.1.1.1.3", + ".1.3.6.1.4.1.9.9.221.1.1.1.1.7", + ".1.3.6.1.4.1.9.9.221.1.1.1.1.18", + ".1.3.6.1.4.1.9.9.221.1.1.1.1.8", + ".1.3.6.1.4.1.9.9.221.1.1.1.1.20") if err == nil { return pools, err } - return c.getMemoryComponentMemoryUsage(ctx, ".1.3.6.1.4.1.9.9.48.1.1.1.2", ".1.3.6.1.4.1.9.9.48.1.1.1.5", "", ".1.3.6.1.4.1.9.9.48.1.1.1.6", "") + return c.getMemoryComponentMemoryUsage(ctx, + ".1.3.6.1.4.1.9.9.48.1.1.1.2", + ".1.3.6.1.4.1.9.9.48.1.1.1.5", + "", + ".1.3.6.1.4.1.9.9.48.1.1.1.6", + "") } // GetMemoryComponentMemoryUsage returns the memory usage of ios devices. @@ -220,3 +231,73 @@ func (c *iosCommunicator) getMemoryDecimalValue(ctx context.Context, con *networ return num, nil } + +func (c *iosCommunicator) GetHardwareHealthComponentPowerSupply(ctx context.Context) ([]device.HardwareHealthComponentPowerSupply, error) { + res, err := c.deviceClass.GetHardwareHealthComponentPowerSupply(ctx) + if err != nil { + return nil, err + } + + for i := 1; i <= 3; i++ { + chassisPS, err := c.getChassisPowerSupply(ctx, i) + if err == nil { + res = append(res, chassisPS) + } + } + + return res, nil +} + +func (c *iosCommunicator) getChassisPowerSupply(ctx context.Context, id int) (device.HardwareHealthComponentPowerSupply, error) { + var chassisPsXStatus network.OID + switch id { + case 1: + chassisPsXStatus = "1.3.6.1.4.1.9.5.1.2.4" + case 2: + chassisPsXStatus = "1.3.6.1.4.1.9.5.1.2.7" + case 3: + chassisPsXStatus = "1.3.6.1.4.1.9.5.1.2.21" + default: + return device.HardwareHealthComponentPowerSupply{}, errors.New("invalid power supply id given") + } + + con, ok := network.DeviceConnectionFromContext(ctx) + if !ok || con.SNMP == nil { + return device.HardwareHealthComponentPowerSupply{}, errors.New("no device connection available") + } + + response, err := con.SNMP.SnmpClient.SNMPWalk(ctx, chassisPsXStatus) + if err != nil { + return device.HardwareHealthComponentPowerSupply{}, errors.Wrap(err, "failed to get 'chassisPs3Status'") + } + + if len(response) == 1 { + val, err := response[0].GetValue() + if err != nil { + return device.HardwareHealthComponentPowerSupply{}, errors.Wrap(err, "failed to get 'chassisPs3Status' value") + } + var state device.HardwareHealthComponentState + switch val.String() { + // other + case "1": + // power supply not present + // ok + case "2": + state = device.HardwareHealthComponentStateNormal + // minorFault + case "3": + state = device.HardwareHealthComponentStateWarning + // majorFault + case "4": + state = device.HardwareHealthComponentStateCritical + } + if state != "" { + descr := "chassis_" + strconv.Itoa(id) + return device.HardwareHealthComponentPowerSupply{ + Description: &descr, + State: &state, + }, nil + } + } + return device.HardwareHealthComponentPowerSupply{}, errors.New("power supply not found") +} diff --git a/config/codecommunicator/junos.go b/config/codecommunicator/junos.go index 6554bf1..9a8a241 100644 --- a/config/codecommunicator/junos.go +++ b/config/codecommunicator/junos.go @@ -84,9 +84,10 @@ func (c *junosCommunicator) addVLANsELS(ctx context.Context, interfaces []device oid := response.GetOID() oidSplit := strings.Split(oid.String(), ".") filterID := vlanIndexFilterID[oidSplit[len(oidSplit)-1]] + nameString := name.String() filterIDVLAN[filterID] = device.VLAN{ - Name: name.String(), + Name: &nameString, } } @@ -192,7 +193,8 @@ func (c *junosCommunicator) addVLANsNonELS(ctx context.Context, interfaces []dev oidSplit := strings.Split(oid.String(), ".") if vlan, ok := vlanIndexVLAN[oidSplit[len(oidSplit)-1]]; ok { - vlan.Name = name.String() + vlanName := name.String() + vlan.Name = &vlanName vlanIndexVLAN[oidSplit[len(oidSplit)-1]] = vlan } } diff --git a/internal/device/device.go b/internal/device/device.go index 0122979..7c740d5 100644 --- a/internal/device/device.go +++ b/internal/device/device.go @@ -233,7 +233,7 @@ type OpticalOPMInterface struct { // // swagger:model type OpticalChannel struct { - Channel string `yaml:"channel,omitempty" json:"channel,omitempty" xml:"channel,omitempty" mapstructure:"channel"` + Channel *string `yaml:"channel,omitempty" json:"channel,omitempty" xml:"channel,omitempty" mapstructure:"channel"` RXPower *float64 `yaml:"rx_power,omitempty" json:"rx_power,omitempty" xml:"rx_power,omitempty" mapstructure:"rx_power"` TXPower *float64 `yaml:"tx_power,omitempty" json:"tx_power,omitempty" xml:"tx_power,omitempty" mapstructure:"tx_power"` } @@ -263,7 +263,7 @@ type VLANInformation struct { // // swagger:model type VLAN struct { - Name string `yaml:"name,omitempty" json:"name,omitempty" xml:"name,omitempty" mapstructure:"name"` + Name *string `yaml:"name,omitempty" json:"name,omitempty" xml:"name,omitempty" mapstructure:"name"` Status *string `yaml:"status,omitempty" json:"status,omitempty" xml:"status,omitempty" mapstructure:"status"` } @@ -383,13 +383,13 @@ type SBCComponent struct { // // swagger:model type SBCComponentAgent struct { - Hostname string `yaml:"hostname" json:"hostname" xml:"hostname" mapstructure:"hostname"` - CurrentActiveSessionsInbound *int `yaml:"current_active_sessions_inbound" json:"current_active_sessions_inbound" xml:"current_active_sessions_inbound" mapstructure:"current_active_sessions_inbound"` - CurrentSessionRateInbound *int `yaml:"current_session_rate_inbound" json:"current_session_rate_inbound" xml:"current_session_rate_inbound" mapstructure:"current_session_rate_inbound"` - CurrentActiveSessionsOutbound *int `yaml:"current_active_sessions_outbound" json:"current_active_sessions_outbound" xml:"current_active_sessions_outbound" mapstructure:"current_active_sessions_outbound"` - CurrentSessionRateOutbound *int `yaml:"current_session_rate_outbound" json:"current_session_rate_outbound" xml:"current_session_rate_outbound" mapstructure:"current_session_rate_outbound"` - PeriodASR *int `yaml:"period_asr" json:"period_asr" xml:"period_asr" mapstructure:"period_asr"` - Status *int `yaml:"status" json:"status" xml:"status" mapstructure:"status"` + Hostname *string `yaml:"hostname" json:"hostname" xml:"hostname" mapstructure:"hostname"` + CurrentActiveSessionsInbound *int `yaml:"current_active_sessions_inbound" json:"current_active_sessions_inbound" xml:"current_active_sessions_inbound" mapstructure:"current_active_sessions_inbound"` + CurrentSessionRateInbound *int `yaml:"current_session_rate_inbound" json:"current_session_rate_inbound" xml:"current_session_rate_inbound" mapstructure:"current_session_rate_inbound"` + CurrentActiveSessionsOutbound *int `yaml:"current_active_sessions_outbound" json:"current_active_sessions_outbound" xml:"current_active_sessions_outbound" mapstructure:"current_active_sessions_outbound"` + CurrentSessionRateOutbound *int `yaml:"current_session_rate_outbound" json:"current_session_rate_outbound" xml:"current_session_rate_outbound" mapstructure:"current_session_rate_outbound"` + PeriodASR *int `yaml:"period_asr" json:"period_asr" xml:"period_asr" mapstructure:"period_asr"` + Status *int `yaml:"status" json:"status" xml:"status" mapstructure:"status"` } // SBCComponentRealm @@ -398,14 +398,14 @@ type SBCComponentAgent struct { // // swagger:model type SBCComponentRealm struct { - Name string `yaml:"name" json:"name" xml:"name"` - CurrentActiveSessionsInbound *int `yaml:"current_active_sessions_inbound" json:"current_active_sessions_inbound" xml:"current_active_sessions_inbound" mapstructure:"current_active_sessions_inbound"` - CurrentSessionRateInbound *int `yaml:"current_session_rate_inbound" json:"current_session_rate_inbound" xml:"current_session_rate_inbound" mapstructure:"current_session_rate_inbound"` - CurrentActiveSessionsOutbound *int `yaml:"current_active_sessions_outbound" json:"current_active_sessions_outbound" xml:"current_active_sessions_outbound" mapstructure:"current_active_sessions_outbound"` - CurrentSessionRateOutbound *int `yaml:"current_session_rate_outbound" json:"current_session_rate_outbound" xml:"current_session_rate_outbound" mapstructure:"current_session_rate_outbound"` - PeriodASR *int `yaml:"period_asr" json:"period_asr" xml:"period_asr" mapstructure:"d_asr"` - ActiveLocalContacts *int `yaml:"active_local_contacts" json:"active_local_contacts" xml:"active_local_contacts" mapstructure:"active_local_contacts"` - Status *int `yaml:"status" json:"status" xml:"status" mapstructure:"status"` + Name *string `yaml:"name" json:"name" xml:"name"` + CurrentActiveSessionsInbound *int `yaml:"current_active_sessions_inbound" json:"current_active_sessions_inbound" xml:"current_active_sessions_inbound" mapstructure:"current_active_sessions_inbound"` + CurrentSessionRateInbound *int `yaml:"current_session_rate_inbound" json:"current_session_rate_inbound" xml:"current_session_rate_inbound" mapstructure:"current_session_rate_inbound"` + CurrentActiveSessionsOutbound *int `yaml:"current_active_sessions_outbound" json:"current_active_sessions_outbound" xml:"current_active_sessions_outbound" mapstructure:"current_active_sessions_outbound"` + CurrentSessionRateOutbound *int `yaml:"current_session_rate_outbound" json:"current_session_rate_outbound" xml:"current_session_rate_outbound" mapstructure:"current_session_rate_outbound"` + PeriodASR *int `yaml:"period_asr" json:"period_asr" xml:"period_asr" mapstructure:"d_asr"` + ActiveLocalContacts *int `yaml:"active_local_contacts" json:"active_local_contacts" xml:"active_local_contacts" mapstructure:"active_local_contacts"` + Status *int `yaml:"status" json:"status" xml:"status" mapstructure:"status"` } // HardwareHealthComponent @@ -455,23 +455,34 @@ type HardwareHealthComponentVoltage struct { type HardwareHealthComponentState string +const ( + HardwareHealthComponentStateInitial HardwareHealthComponentState = "initial" + HardwareHealthComponentStateNormal HardwareHealthComponentState = "normal" + HardwareHealthComponentStateWarning HardwareHealthComponentState = "warning" + HardwareHealthComponentStateCritical HardwareHealthComponentState = "critical" + HardwareHealthComponentStateShutdown HardwareHealthComponentState = "shutdown" + HardwareHealthComponentStateNotPresent HardwareHealthComponentState = "not_present" + HardwareHealthComponentStateNotFunctioning HardwareHealthComponentState = "not_functioning" + HardwareHealthComponentStateUnknown HardwareHealthComponentState = "unknown" +) + func (h HardwareHealthComponentState) GetInt() (int, error) { switch h { - case "initial": + case HardwareHealthComponentStateInitial: return 0, nil - case "normal": + case HardwareHealthComponentStateNormal: return 1, nil - case "warning": + case HardwareHealthComponentStateWarning: return 2, nil - case "critical": + case HardwareHealthComponentStateCritical: return 3, nil - case "shutdown": + case HardwareHealthComponentStateShutdown: return 4, nil - case "not_present": + case HardwareHealthComponentStateNotPresent: return 5, nil - case "not_functioning": + case HardwareHealthComponentStateNotFunctioning: return 6, nil - case "unknown": + case HardwareHealthComponentStateUnknown: return 7, nil } return 7, fmt.Errorf("invalid hardware health state '%s'", h) diff --git a/internal/request/check_interface_metrics_request_process.go b/internal/request/check_interface_metrics_request_process.go index 506b793..0456b60 100644 --- a/internal/request/check_interface_metrics_request_process.go +++ b/internal/request/check_interface_metrics_request_process.go @@ -459,17 +459,19 @@ func addCheckInterfacePerformanceData(interfaces []device.Interface, r *monitori } for _, channel := range i.DWDM.Channels { - if channel.RXPower != nil { - err := r.AddPerformanceDataPoint(monitoringplugin.NewPerformanceDataPoint("rx_power", *channel.RXPower).SetLabel(*i.IfDescr + "_" + channel.Channel)) - if err != nil { - return err + if channel.Channel != nil { + if channel.RXPower != nil { + err := r.AddPerformanceDataPoint(monitoringplugin.NewPerformanceDataPoint("rx_power", *channel.RXPower).SetLabel(*i.IfDescr + "_" + *channel.Channel)) + if err != nil { + return err + } } - } - if channel.TXPower != nil { - err := r.AddPerformanceDataPoint(monitoringplugin.NewPerformanceDataPoint("tx_power", *channel.TXPower).SetLabel(*i.IfDescr + "_" + channel.Channel)) - if err != nil { - return err + if channel.TXPower != nil { + err := r.AddPerformanceDataPoint(monitoringplugin.NewPerformanceDataPoint("tx_power", *channel.TXPower).SetLabel(*i.IfDescr + "_" + *channel.Channel)) + if err != nil { + return err + } } } } @@ -534,8 +536,8 @@ func addCheckInterfacePerformanceData(interfaces []device.Interface, r *monitori } } for _, channel := range i.OpticalOPM.Channels { - if channel.RXPower != nil { - err := r.AddPerformanceDataPoint(monitoringplugin.NewPerformanceDataPoint("rx_power", *channel.RXPower).SetLabel(*i.IfDescr + "_" + channel.Channel)) + if channel.Channel != nil && channel.RXPower != nil { + err := r.AddPerformanceDataPoint(monitoringplugin.NewPerformanceDataPoint("rx_power", *channel.RXPower).SetLabel(*i.IfDescr + "_" + *channel.Channel)) if err != nil { return err } diff --git a/internal/request/check_sbc_request_process.go b/internal/request/check_sbc_request_process.go index ab3dadc..b0eb524 100644 --- a/internal/request/check_sbc_request_process.go +++ b/internal/request/check_sbc_request_process.go @@ -77,8 +77,12 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } for _, agent := range sbc.Agents { + if agent.Hostname == nil { + continue + } + if agent.CurrentActiveSessionsInbound != nil { - p := monitoringplugin.NewPerformanceDataPoint("current_active_sessions_inbound", *agent.CurrentActiveSessionsInbound).SetLabel(agent.Hostname) + p := monitoringplugin.NewPerformanceDataPoint("current_active_sessions_inbound", *agent.CurrentActiveSessionsInbound).SetLabel(*agent.Hostname) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -87,7 +91,7 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } if agent.CurrentSessionRateInbound != nil { - p := monitoringplugin.NewPerformanceDataPoint("current_session_rate_inbound", *agent.CurrentSessionRateInbound).SetLabel(agent.Hostname) + p := monitoringplugin.NewPerformanceDataPoint("current_session_rate_inbound", *agent.CurrentSessionRateInbound).SetLabel(*agent.Hostname) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -96,7 +100,7 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } if agent.CurrentActiveSessionsOutbound != nil { - p := monitoringplugin.NewPerformanceDataPoint("current_active_sessions_outbound", *agent.CurrentActiveSessionsOutbound).SetLabel(agent.Hostname) + p := monitoringplugin.NewPerformanceDataPoint("current_active_sessions_outbound", *agent.CurrentActiveSessionsOutbound).SetLabel(*agent.Hostname) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -105,7 +109,7 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } if agent.CurrentSessionRateOutbound != nil { - p := monitoringplugin.NewPerformanceDataPoint("current_session_rate_outbound", *agent.CurrentSessionRateOutbound).SetLabel(agent.Hostname) + p := monitoringplugin.NewPerformanceDataPoint("current_session_rate_outbound", *agent.CurrentSessionRateOutbound).SetLabel(*agent.Hostname) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -114,7 +118,7 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } if agent.PeriodASR != nil { - p := monitoringplugin.NewPerformanceDataPoint("period_asr", *agent.PeriodASR).SetLabel(agent.Hostname) + p := monitoringplugin.NewPerformanceDataPoint("period_asr", *agent.PeriodASR).SetLabel(*agent.Hostname) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -123,7 +127,7 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } if agent.Status != nil { - p := monitoringplugin.NewPerformanceDataPoint("status", *agent.Status).SetLabel(agent.Hostname) + p := monitoringplugin.NewPerformanceDataPoint("status", *agent.Status).SetLabel(*agent.Hostname) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -133,8 +137,12 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } for _, realm := range sbc.Realms { + if realm.Name == nil { + continue + } + if realm.CurrentActiveSessionsInbound != nil { - p := monitoringplugin.NewPerformanceDataPoint("current_active_sessions_inbound", *realm.CurrentActiveSessionsInbound).SetLabel(realm.Name) + p := monitoringplugin.NewPerformanceDataPoint("current_active_sessions_inbound", *realm.CurrentActiveSessionsInbound).SetLabel(*realm.Name) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -143,7 +151,7 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } if realm.CurrentSessionRateInbound != nil { - p := monitoringplugin.NewPerformanceDataPoint("current_session_rate_inbound", *realm.CurrentSessionRateInbound).SetLabel(realm.Name) + p := monitoringplugin.NewPerformanceDataPoint("current_session_rate_inbound", *realm.CurrentSessionRateInbound).SetLabel(*realm.Name) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -152,7 +160,7 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } if realm.CurrentActiveSessionsOutbound != nil { - p := monitoringplugin.NewPerformanceDataPoint("current_active_sessions_outbound", *realm.CurrentActiveSessionsOutbound).SetLabel(realm.Name) + p := monitoringplugin.NewPerformanceDataPoint("current_active_sessions_outbound", *realm.CurrentActiveSessionsOutbound).SetLabel(*realm.Name) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -161,7 +169,7 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } if realm.CurrentSessionRateOutbound != nil { - p := monitoringplugin.NewPerformanceDataPoint("current_session_rate_outbound", *realm.CurrentSessionRateOutbound).SetLabel(realm.Name) + p := monitoringplugin.NewPerformanceDataPoint("current_session_rate_outbound", *realm.CurrentSessionRateOutbound).SetLabel(*realm.Name) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -170,7 +178,7 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } if realm.PeriodASR != nil { - p := monitoringplugin.NewPerformanceDataPoint("period_asr", *realm.PeriodASR).SetLabel(realm.Name) + p := monitoringplugin.NewPerformanceDataPoint("period_asr", *realm.PeriodASR).SetLabel(*realm.Name) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -179,7 +187,7 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } if realm.Status != nil { - p := monitoringplugin.NewPerformanceDataPoint("status", *realm.Status).SetLabel(realm.Name) + p := monitoringplugin.NewPerformanceDataPoint("status", *realm.Status).SetLabel(*realm.Name) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) @@ -188,7 +196,7 @@ func (r *CheckSBCRequest) process(ctx context.Context) (Response, error) { } if realm.ActiveLocalContacts != nil { - p := monitoringplugin.NewPerformanceDataPoint("active_local_contacts", *realm.ActiveLocalContacts).SetLabel(realm.Name) + p := monitoringplugin.NewPerformanceDataPoint("active_local_contacts", *realm.ActiveLocalContacts).SetLabel(*realm.Name) err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) From ff07ed63aae088a38bbd1a5ce26f455dddcf0912 Mon Sep 17 00:00:00 2001 From: babos77 Date: Thu, 30 Sep 2021 11:28:53 +0200 Subject: [PATCH 5/7] check hardware-health --- cmd/check_hardware_health.go | 12 +- .../request/check_hardware_health_process.go | 142 ++++++++++++++++-- internal/request/check_request.go | 40 +++++ 3 files changed, 182 insertions(+), 12 deletions(-) diff --git a/cmd/check_hardware_health.go b/cmd/check_hardware_health.go index 885b133..271c1d3 100644 --- a/cmd/check_hardware_health.go +++ b/cmd/check_hardware_health.go @@ -1,6 +1,7 @@ package cmd import ( + "github.com/inexio/thola/internal/device" "github.com/inexio/thola/internal/request" "github.com/spf13/cobra" ) @@ -13,7 +14,16 @@ func init() { var checkHardwareHealthCMD = &cobra.Command{ Use: "hardware-health", Short: "Check hardware-health of a device.", - Long: "Check hardware-health of a device and return various performance data.", + Long: "Check hardware-health of a device and return various performance data.\n" + + "Performance data include states for temperatures, power supply, fans, etc. with the following meanings:\n" + + "\t0: " + string(device.HardwareHealthComponentStateInitial) + "\n" + + "\t1: " + string(device.HardwareHealthComponentStateNormal) + "\n" + + "\t2: " + string(device.HardwareHealthComponentStateWarning) + "\n" + + "\t3: " + string(device.HardwareHealthComponentStateCritical) + "\n" + + "\t4: " + string(device.HardwareHealthComponentStateShutdown) + "\n" + + "\t5: " + string(device.HardwareHealthComponentStateNotPresent) + "\n" + + "\t6: " + string(device.HardwareHealthComponentStateNotFunctioning) + "\n" + + "\t7: " + string(device.HardwareHealthComponentStateUnknown), Run: func(cmd *cobra.Command, args []string) { r := request.CheckHardwareHealthRequest{ CheckDeviceRequest: getCheckDeviceRequest(args[0]), diff --git a/internal/request/check_hardware_health_process.go b/internal/request/check_hardware_health_process.go index d1c586a..9db8b02 100644 --- a/internal/request/check_hardware_health_process.go +++ b/internal/request/check_hardware_health_process.go @@ -5,17 +5,21 @@ package request import ( "context" "github.com/inexio/go-monitoringplugin" + "github.com/inexio/thola/internal/device" ) func (r *CheckHardwareHealthRequest) process(ctx context.Context) (Response, error) { r.init() - hhRequest := ReadHardwareHealthRequest{ReadRequest{r.BaseRequest}} - response, err := hhRequest.process(ctx) - if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while processing read hardware health request", true) { + com, err := GetCommunicator(ctx, r.BaseRequest) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while getting communicator", true) { + return &CheckResponse{r.mon.GetInfo()}, nil + } + + res, err := com.GetHardwareHealthComponent(ctx) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while reading hardware-health", true) { return &CheckResponse{r.mon.GetInfo()}, nil } - res := response.(*ReadHardwareHealthResponse) if res.EnvironmentMonitorState != nil { stateInt, err := (*res.EnvironmentMonitorState).GetInt() @@ -32,8 +36,13 @@ func (r *CheckHardwareHealthRequest) process(ctx context.Context) (Response, err r.mon.UpdateStatusIf((*res.EnvironmentMonitorState) != "normal", monitoringplugin.CRITICAL, "environment monitor state is critical") } + // check duplicate labels + duplicateLabelCheckerFans := make(duplicateLabelChecker) for _, fan := range res.Fans { - if r.mon.UpdateStatusIf(fan.State == nil || fan.Description == nil, monitoringplugin.UNKNOWN, "description or state is missing for fan") { + duplicateLabelCheckerFans.addLabel(fan.Description) + } + for _, fan := range res.Fans { + if r.mon.UpdateStatusIf(fan.State == nil, monitoringplugin.UNKNOWN, "description or state is missing for fan") { r.mon.PrintPerformanceData(false) return &CheckResponse{r.mon.GetInfo()}, nil } @@ -44,14 +53,27 @@ func (r *CheckHardwareHealthRequest) process(ctx context.Context) (Response, err return &CheckResponse{r.mon.GetInfo()}, nil } - p := monitoringplugin.NewPerformanceDataPoint("fan_state", stateInt).SetLabel(*fan.Description) + p := monitoringplugin.NewPerformanceDataPoint("fan_state", stateInt) + + if label := duplicateLabelCheckerFans.getModifiedLabel(fan.Description); label != "" { + p.SetLabel(label) + } + err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) return &CheckResponse{r.mon.GetInfo()}, nil } + + r.mon.UpdateStatusIf(*fan.State == device.HardwareHealthComponentStateWarning, monitoringplugin.WARNING, "fan state is warning") + r.mon.UpdateStatusIf(*fan.State == device.HardwareHealthComponentStateCritical, monitoringplugin.CRITICAL, "fan state is critical") } + // check duplicate labels + duplicateLabelCheckerPS := make(duplicateLabelChecker) + for _, ps := range res.PowerSupply { + duplicateLabelCheckerPS.addLabel(ps.Description) + } for _, powerSupply := range res.PowerSupply { if r.mon.UpdateStatusIf(powerSupply.State == nil, monitoringplugin.UNKNOWN, "state is missing for power supply") { r.mon.PrintPerformanceData(false) @@ -65,17 +87,115 @@ func (r *CheckHardwareHealthRequest) process(ctx context.Context) (Response, err } p := monitoringplugin.NewPerformanceDataPoint("power_supply_state", stateInt) - if powerSupply.Description != nil { - p.SetLabel(*powerSupply.Description) - } else if r.mon.UpdateStatusIf(len(res.PowerSupply) != 1, monitoringplugin.UNKNOWN, "description is missing for power supply") { - r.mon.PrintPerformanceData(false) - return &CheckResponse{r.mon.GetInfo()}, nil + + if label := duplicateLabelCheckerPS.getModifiedLabel(powerSupply.Description); label != "" { + p.SetLabel(label) } + err = r.mon.AddPerformanceDataPoint(p) if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { r.mon.PrintPerformanceData(false) return &CheckResponse{r.mon.GetInfo()}, nil } + + r.mon.UpdateStatusIf(*powerSupply.State == device.HardwareHealthComponentStateWarning, monitoringplugin.WARNING, "power supply state is warning") + r.mon.UpdateStatusIf(*powerSupply.State == device.HardwareHealthComponentStateCritical, monitoringplugin.CRITICAL, "power supply state is critical") + } + + // check duplicate labels + duplicateLabelCheckerTemp := make(duplicateLabelChecker) + for _, t := range res.Temperature { + duplicateLabelCheckerTemp.addLabel(t.Description) + } + for _, temp := range res.Temperature { + if r.mon.UpdateStatusIf(temp.State == nil && temp.Temperature == nil, monitoringplugin.UNKNOWN, "temperature sensor has no state and temperature value") { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + + if temp.Temperature != nil { + p := monitoringplugin.NewPerformanceDataPoint("temperature", *temp.Temperature) + + if label := duplicateLabelCheckerTemp.getModifiedLabel(temp.Description); label != "" { + p.SetLabel(label) + } + + err = r.mon.AddPerformanceDataPoint(p) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + } + if temp.State != nil { + stateInt, err := (*temp.State).GetInt() + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "read out invalid hardware health component state for temperature", true) { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + + p := monitoringplugin.NewPerformanceDataPoint("temperature_state", stateInt) + + if label := duplicateLabelCheckerTemp.getModifiedLabel(temp.Description); label != "" { + p.SetLabel(label) + } + + err = r.mon.AddPerformanceDataPoint(p) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + + r.mon.UpdateStatusIf(*temp.State == device.HardwareHealthComponentStateWarning, monitoringplugin.WARNING, "temperature state is warning") + r.mon.UpdateStatusIf(*temp.State == device.HardwareHealthComponentStateCritical, monitoringplugin.CRITICAL, "temperature state is critical") + } + } + + // check duplicate labels + duplicateLabelCheckerVolt := make(duplicateLabelChecker) + for _, v := range res.Voltage { + duplicateLabelCheckerVolt.addLabel(v.Description) + } + for _, volt := range res.Voltage { + if r.mon.UpdateStatusIf(volt.State == nil && volt.Voltage == nil, monitoringplugin.UNKNOWN, "voltage sensor has no state and voltage value") { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + + if volt.Voltage != nil { + p := monitoringplugin.NewPerformanceDataPoint("voltage", *volt.Voltage) + + if label := duplicateLabelCheckerVolt.getModifiedLabel(volt.Description); label != "" { + p.SetLabel(label) + } + + err = r.mon.AddPerformanceDataPoint(p) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + } + if volt.State != nil { + stateInt, err := (*volt.State).GetInt() + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "read out invalid hardware health component state for voltage", true) { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + + p := monitoringplugin.NewPerformanceDataPoint("voltage_state", stateInt) + + if label := duplicateLabelCheckerVolt.getModifiedLabel(volt.Description); label != "" { + p.SetLabel(label) + } + + err = r.mon.AddPerformanceDataPoint(p) + if r.mon.UpdateStatusOnError(err, monitoringplugin.UNKNOWN, "error while adding performance data point", true) { + r.mon.PrintPerformanceData(false) + return &CheckResponse{r.mon.GetInfo()}, nil + } + + r.mon.UpdateStatusIf(*volt.State == device.HardwareHealthComponentStateWarning, monitoringplugin.WARNING, "voltage state is warning") + r.mon.UpdateStatusIf(*volt.State == device.HardwareHealthComponentStateCritical, monitoringplugin.CRITICAL, "voltage state is critical") + } } return &CheckResponse{r.mon.GetInfo()}, nil diff --git a/internal/request/check_request.go b/internal/request/check_request.go index 1bafc27..d6e9b04 100644 --- a/internal/request/check_request.go +++ b/internal/request/check_request.go @@ -2,6 +2,7 @@ package request import ( "github.com/inexio/go-monitoringplugin" + "strconv" ) // CheckRequest @@ -28,6 +29,45 @@ func (r *CheckRequest) handlePreProcessError(err error) (Response, error) { return &CheckResponse{r.mon.GetInfo()}, nil } +type labelCounter struct { + duplicated bool + current int +} + +type duplicateLabelChecker map[string]*labelCounter + +func (d *duplicateLabelChecker) addLabel(label *string) { + l := "" + if label != nil { + l = *label + } + if lc, ok := (*d)[l]; ok { + lc.duplicated = true + } else { + (*d)[l] = &labelCounter{ + duplicated: false, + current: 1, + } + } +} + +func (d *duplicateLabelChecker) getModifiedLabel(label *string) string { + l := "" + if label != nil { + l = *label + } + if lc, ok := (*d)[l]; ok { + if lc.duplicated { + if l != "" { + l += "_" + } + l += strconv.Itoa(lc.current) + lc.current++ + } + } + return l +} + // CheckResponse // // CheckResponse is a generic response struct for the check plugin format. From 83f3c22008f91779f3bf9d2e8f23d0e7981434d3 Mon Sep 17 00:00:00 2001 From: mikameyer Date: Thu, 30 Sep 2021 12:10:40 +0200 Subject: [PATCH 6/7] fix type & cleanup --- internal/request/check_hardware_health_process.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/request/check_hardware_health_process.go b/internal/request/check_hardware_health_process.go index 9db8b02..b7509d2 100644 --- a/internal/request/check_hardware_health_process.go +++ b/internal/request/check_hardware_health_process.go @@ -33,7 +33,7 @@ func (r *CheckHardwareHealthRequest) process(ctx context.Context) (Response, err return &CheckResponse{r.mon.GetInfo()}, nil } - r.mon.UpdateStatusIf((*res.EnvironmentMonitorState) != "normal", monitoringplugin.CRITICAL, "environment monitor state is critical") + r.mon.UpdateStatusIf((*res.EnvironmentMonitorState) != device.HardwareHealthComponentStateNormal, monitoringplugin.CRITICAL, "environment monitor state is critical") } // check duplicate labels @@ -42,7 +42,7 @@ func (r *CheckHardwareHealthRequest) process(ctx context.Context) (Response, err duplicateLabelCheckerFans.addLabel(fan.Description) } for _, fan := range res.Fans { - if r.mon.UpdateStatusIf(fan.State == nil, monitoringplugin.UNKNOWN, "description or state is missing for fan") { + if r.mon.UpdateStatusIf(fan.State == nil, monitoringplugin.UNKNOWN, "state is missing for fan") { r.mon.PrintPerformanceData(false) return &CheckResponse{r.mon.GetInfo()}, nil } From b5f9531b230935eeb8600ace4f58f53b8a806fc3 Mon Sep 17 00:00:00 2001 From: mikameyer Date: Fri, 1 Oct 2021 15:00:29 +0200 Subject: [PATCH 7/7] Update README.md --- README.md | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index dc4c1a1..370f673 100644 --- a/README.md +++ b/README.md @@ -34,35 +34,27 @@ Thola currently has three main modes of operation with various subcommands: - `identify` automatically identifies the device and outputs its vendor, model and other properties. - `read` reads out values and statistics of the device. - `read available-components` returns the available components for the device. - - `read interfaces` outputs the interfaces with several values like error counters and statistics. - `read count-interfaces` counts the interfaces. - `read cpu-load` returns the current cpu load of all CPUs. + - `read disk` reads storage utilization. + - `read hardware-health` reads hardware health information like temperatures and fans. + - `read interfaces` outputs the interfaces with several values like error counters and statistics. + - `read sbc` reads out SBC specific information. - `read memory-usage` reads out the current memory usage. - - `read disk` reads storage utilizations. - `read server` outputs server specific information like users and process count. - `read ups` outputs the special values of a UPS device. - - `read sbc` reads out SBC specific information. - `check` performs checks that can be used in monitoring systems. Output is by default in check plugin format. + - `check cpu-load` checks the average CPU load of all CPUs against given thresholds and outputs the current load of all CPUs as performance data. + - `check disk` checks the free space of storages. + - `check hardware-health` checks the hardware-health of a device. - `check identify` compares the device properties with given expectations. - - `check snmp` checks SNMP reachability. - `check interface-metrics` outputs performance data for the interfaces, including special values based on the interface type (e.g. Radio Interface). - - `check cpu-load` checks the average CPU load of all CPUs against given thresholds and outputs the current load of all CPUs as performance data. - `check memory-usage` checks the current memory usage against given thresholds. - - `check ups` checks if a UPS device has its main voltage applied and outputs additional performance data like battery capacity or current load, and compares them to optionally given thresholds. - - `check disk` checks the free space of storages. + - `check sbc` checks an SBC device and outputs metrics for each realm and agent as performance data. - `check server` checks server specific information. + - `check snmp` checks SNMP reachability. + - `check ups` checks if a UPS device has its main voltage applied and outputs additional performance data like battery capacity or current load, and compares them to optionally given thresholds. - `check thola-server` checks reachability of a Thola API. - - `check sbc` checks an SBC device and outputs metrics for each realm and agent as performance data. - -More features are coming soon: - -- Read out additional information - - Inventory data - - Sensors and Status Flags like temperatures, frequencies, alarms, etc. - - Device specific data (e.g. DSLAMs) -- More checks - - Hardware health - - Device specific checks ## Quick Start