Repository: kentik/ktranslate
Pull request #296 (2022-03-18 21:38:16)

Adding a docker-compose example: #293
Dropping in a docker-compose file for running ktranslate in snmp collection (NOT POLLING) mode. 


Patch:
diff --git a/deployment/docker-compose/README b/deployment/docker-compose/README
new file mode 100644
index 0000000..ead9242
--- /dev/null
+++ b/deployment/docker-compose/README
@@ -0,0 +1,3 @@
+Run docker-compose with:
+
+NEW_RELIC_API_KEY=$MY_KEY NR_ACCOUNT_ID=$MY_ACCOUNT KT_SNMP_PATH=$PATH/TO/SNMP.yaml NR_REGION=US docker-compose up
\ No newline at end of file
diff --git a/deployment/docker-compose/docker-compose.yaml b/deployment/docker-compose/docker-compose.yaml
new file mode 100644
index 0000000..91d4749
--- /dev/null
+++ b/deployment/docker-compose/docker-compose.yaml
@@ -0,0 +1,18 @@
+version: "3.9"
+services:
+  ktranslate:
+    image: kentik/ktranslate:v2
+    container_name: ktranslate
+    restart: unless-stopped
+    volumes:
+      - ${KT_SNMP_PATH}:/snmp.yaml ## Make KT_SNMP_PATH a path to your local snmp.yaml config.
+    environment: ## Set these env vars
+      - NEW_RELIC_API_KEY=${NEW_RELIC_API_KEY}
+      - NR_ACCOUNT_ID=${NR_ACCOUNT_ID}
+      - NR_REGION=${NR_REGION}
+    command:
+      - -snmp=/snmp.yaml
+      - -metrics=jchf
+      - -tee_logs=true
+      - nr1.snmp
+    network_mode: host
\ No newline at end of file
diff --git a/pkg/inputs/snmp/metadata/device_metadata.go b/pkg/inputs/snmp/metadata/device_metadata.go
index a27bdbc..1d5a7d0 100644
--- a/pkg/inputs/snmp/metadata/device_metadata.go
+++ b/pkg/inputs/snmp/metadata/device_metadata.go
@@ -109,10 +109,10 @@ func (dm *DeviceMetadata) poll(ctx context.Context, server *gosnmp.GoSNMP) (*kt.
 		if len(oidResults) == 0 {
 			missing++
 			if _, ok := dm.missing[oid]; ok {
-				dm.log.Debugf("OID %s failed to return results, Metric Name: %s", oid, mib.Name)
+				dm.log.Debugf("OID %s failed to return results, Metric Name: %s", oid, mib.GetName())
 			} else {
 				dm.missing[oid] = true
-				dm.log.Warnf("OID %s failed to return results, Metric Name: %s", oid, mib.Name)
+				dm.log.Warnf("OID %s failed to return results, Metric Name: %s", oid, mib.GetName())
 			}
 		}
 		for _, result := range oidResults {
diff --git a/pkg/inputs/snmp/util/util.go b/pkg/inputs/snmp/util/util.go
index d388da6..717005a 100644
--- a/pkg/inputs/snmp/util/util.go
+++ b/pkg/inputs/snmp/util/util.go
@@ -100,7 +100,11 @@ func ToInt64(val interface{}) int64 {
 // topology-demo/devicemetrics/main.go.  Prefix must occur in value, otherwise
 // results are undefined (aka "wrong").
 func GetIndex(value, prefix string) string {
-	return value[strings.Index(value, prefix)+len(prefix):]
+	posit := strings.Index(value, prefix) + len(prefix)
+	if len(value) > posit {
+		return value[posit:]
+	}
+	return ""
 }
 
 // walk the OID subtree under a root, returning a slice of varbinds
@@ -125,8 +129,10 @@ func WalkOID(ctx context.Context, device *kt.SnmpDeviceConfig, oid string, serve
 		pollTry{walk: server.WalkAll, sleep: SNMP_POLL_SLEEP_TIME},
 	}
 
-	// If the device says to not use bulkwalkall, trim this out now.
-	if device.NoUseBulkWalkAll {
+	// If we are overriding with a test set.
+	if walker := device.GetTestWalker(); walker != nil {
+		tries = []pollTry{pollTry{walk: walker.WalkAll, sleep: time.Duration(0)}}
+	} else if device.NoUseBulkWalkAll { // If the device says to not use bulkwalkall, trim this out now.
 		tries = tries[1:]
 	}
 
diff --git a/pkg/kt/snmp.go b/pkg/kt/snmp.go
index e9dfa0d..b0733b2 100644
--- a/pkg/kt/snmp.go
+++ b/pkg/kt/snmp.go
@@ -10,6 +10,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/gosnmp/gosnmp"
 	go_metrics "github.com/kentik/go-metrics"
 	"gopkg.in/yaml.v2"
 )
@@ -161,6 +162,7 @@ type SnmpDeviceConfig struct {
 	InstrumentationName string            `yaml:"instrumentationName,omitempty"`
 	RunPing             bool              `yaml:"response_time,omitempty"`
 	allUserTags         map[string]string
+	walker              SNMPTestWalker
 }
 
 type SnmpTrapConfig struct {
@@ -302,6 +304,9 @@ func (mb *Mib) String() string {
 }
 
 func (mb *Mib) GetName() string { // Tag takes precedince over name if it is present.
+	if mb == nil {
+		return "missing_mib"
+	}
 	if mb.Tag != "" {
 		return mb.Tag
 	}
@@ -587,3 +592,15 @@ func (d *SnmpDeviceConfig) GetUserTags() map[string]string {
 
 	return out
 }
+
+func (d *SnmpDeviceConfig) GetTestWalker() SNMPTestWalker {
+	return d.walker
+}
+
+func (d *SnmpDeviceConfig) SetTestWalker(w SNMPTestWalker) {
+	d.walker = w
+}
+
+type SNMPTestWalker interface {
+	WalkAll(string) ([]gosnmp.SnmpPDU, error)
+}


Test patch:
diff --git a/pkg/inputs/snmp/metadata/device_metadata_test.go b/pkg/inputs/snmp/metadata/device_metadata_test.go
new file mode 100644
index 0000000..a6844ad
--- /dev/null
+++ b/pkg/inputs/snmp/metadata/device_metadata_test.go
@@ -0,0 +1,70 @@
+package metadata
+
+import (
+	"context"
+	"errors"
+	"testing"
+
+	"github.com/gosnmp/gosnmp"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/kentik/ktranslate/pkg/eggs/logger"
+	lt "github.com/kentik/ktranslate/pkg/eggs/logger/testing"
+	"github.com/kentik/ktranslate/pkg/kt"
+)
+
+type testWalker struct {
+	results []gosnmp.SnmpPDU
+	err     error
+	dm      string
+}
+
+func (w testWalker) WalkAll(oid string) ([]gosnmp.SnmpPDU, error) {
+	return w.results, w.err
+}
+
+func TestPoll(t *testing.T) {
+	l := lt.NewTestContextL(logger.NilContext, t)
+	conf := &kt.SnmpDeviceConfig{}
+	conf.SetTestWalker(testWalker{err: errors.New("Error"), dm: ""}) // Non nil error
+	dm := NewDeviceMetadata(nil, conf, kt.NewSnmpDeviceMetric(nil, "deviceA"), l)
+
+	res, err := dm.poll(context.Background(), nil)
+	assert.NoError(t, err)
+	assert.Equal(t, 0, len(res.Tables))
+
+	res, err = dm.poll(context.Background(), nil)
+	assert.NoError(t, err)
+	assert.Equal(t, 0, len(res.Tables))
+
+	conf.SetTestWalker(testWalker{results: []gosnmp.SnmpPDU{}, dm: ""}) // No results.
+	res, err = dm.poll(context.Background(), nil)
+	assert.NoError(t, err)
+	assert.Equal(t, 0, len(res.Tables))
+
+	// Test random string
+	conf.SetTestWalker(testWalker{results: []gosnmp.SnmpPDU{{Value: "not a byte-slice", Name: ".1.1.2.3"}}, dm: ""}) // string value.
+	res, err = dm.poll(context.Background(), nil)
+	assert.NoError(t, err)
+	assert.Equal(t, 0, len(res.Tables))
+
+	// Test sysdesc
+	conf.SetTestWalker(testWalker{results: []gosnmp.SnmpPDU{{Value: []byte("sysdesc"), Name: ".1.3.6.1.2.1.1.1.0"}}, dm: ""}) // SysDescr.
+	res, err = dm.poll(context.Background(), nil)
+	assert.NoError(t, err)
+	assert.Equal(t, "sysdesc", res.SysDescr)
+
+	meta := map[string]*kt.Mib{
+		"1.1.1": &kt.Mib{Name: "foo"},
+	}
+	dm = NewDeviceMetadata(meta, conf, kt.NewSnmpDeviceMetric(nil, "deviceA"), l)
+	conf.SetTestWalker(testWalker{results: []gosnmp.SnmpPDU{{Value: "not a byte-slice", Name: ".1.1.1"}}, dm: ""}) // string value.
+	res, err = dm.poll(context.Background(), nil)
+	assert.NoError(t, err)
+	assert.Equal(t, 0, len(res.Tables))
+
+	conf.SetTestWalker(testWalker{results: []gosnmp.SnmpPDU{{Value: "not a byte-slice", Name: ".1.1.1.2"}}, dm: ""}) // table.
+	res, err = dm.poll(context.Background(), nil)
+	assert.NoError(t, err)
+	assert.Equal(t, 1, len(res.Tables))
+}
diff --git a/pkg/kt/snmp_test.go b/pkg/kt/snmp_test.go
index 2fc5fbd..d325318 100644
--- a/pkg/kt/snmp_test.go
+++ b/pkg/kt/snmp_test.go
@@ -20,3 +20,17 @@ func TestIsPollReady(t *testing.T) {
 	assert.False(t, mib.IsPollReady()) // Skip the 2nd.
 	assert.False(t, mib.IsPollReady()) // Skip the 2nd.
 }
+
+func TestGetName(t *testing.T) {
+	mib := &Mib{
+		Tag:  "foo",
+		Name: "name",
+	}
+	assert.Equal(t, "foo", mib.GetName())
+	mib = nil
+	assert.Equal(t, "missing_mib", mib.GetName())
+	mib = &Mib{
+		Name: "bar",
+	}
+	assert.Equal(t, "bar", mib.GetName())
+}