//go:build linux package dpdk import ( "encoding/json" "errors" "fmt" "math/rand" "net" "os" "path/filepath" "strings" "testing" "time" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/filter" "github.com/influxdata/telegraf/internal/globpath" "github.com/influxdata/telegraf/plugins/inputs/dpdk/mocks" "github.com/influxdata/telegraf/testutil" ) func Test_Init(t *testing.T) { t.Run("when SocketPath field isn't set then it should be set to default value", func(t *testing.T) { dpdk := Dpdk{ Log: testutil.Logger{}, SocketPath: "", } require.Empty(t, dpdk.SocketPath) require.NoError(t, dpdk.Init()) require.Equal(t, defaultPathToSocket, dpdk.SocketPath) }) t.Run("when Metadata Fields isn't set then it should be set to default value (dpdk_pid)", func(t *testing.T) { dpdk := Dpdk{ Log: testutil.Logger{}, } require.Nil(t, dpdk.MetadataFields) require.NoError(t, dpdk.Init()) require.Equal(t, []string{dpdkMetadataFieldPidName, dpdkMetadataFieldVersionName}, dpdk.MetadataFields) }) t.Run("when PluginOptions field isn't set then it should be set to default value (in_memory)", func(t *testing.T) { dpdk := Dpdk{ Log: testutil.Logger{}, } require.Nil(t, dpdk.PluginOptions) require.NoError(t, dpdk.Init()) require.Equal(t, []string{dpdkPluginOptionInMemory}, dpdk.PluginOptions) }) t.Run("when commands are in invalid format (doesn't start with '/') then error should be returned", func(t *testing.T) { pathToSocket, _ := createSocketForTest(t, "") dpdk := Dpdk{ Log: testutil.Logger{}, SocketPath: pathToSocket, AdditionalCommands: []string{"invalid"}, } err := dpdk.Init() require.Error(t, err) require.Contains(t, err.Error(), "command should start with slash") }) t.Run("when AccessTime is < 0 then error should be returned", func(t *testing.T) { dpdk := Dpdk{ Log: testutil.Logger{}, AccessTimeout: -1, } err := dpdk.Init() require.Error(t, err) require.Contains(t, err.Error(), "socket_access_timeout should be positive number") }) t.Run("when device_types and additional_commands are empty, then error should be returned", func(t *testing.T) { pathToSocket, _ := createSocketForTest(t, "") dpdk := Dpdk{ SocketPath: pathToSocket, DeviceTypes: make([]string, 0), Log: testutil.Logger{}, } err := dpdk.Init() require.Error(t, err) require.Contains(t, err.Error(), "plugin was configured with nothing to read") }) t.Run("when UnreachableSocketBehavior specified with unknown value - err should be returned", func(t *testing.T) { dpdk := Dpdk{ DeviceTypes: []string{"ethdev"}, Log: testutil.Logger{}, UnreachableSocketBehavior: "whatisthat", } err := dpdk.Init() require.Error(t, err) require.Contains(t, err.Error(), "unreachable_socket_behavior") }) } func Test_Start(t *testing.T) { t.Run("when socket doesn't exist err should be returned", func(t *testing.T) { dpdk := Dpdk{ DeviceTypes: []string{"ethdev"}, Log: testutil.Logger{}, } err := dpdk.Init() require.NoError(t, err) err = dpdk.Start(nil) require.Error(t, err) require.Contains(t, err.Error(), "no active sockets connections present") }) t.Run("when socket doesn't exist, but UnreachableSocketBehavior is Ignore err shouldn't be returned", func(t *testing.T) { dpdk := Dpdk{ DeviceTypes: []string{"ethdev"}, Log: testutil.Logger{}, UnreachableSocketBehavior: unreachableSocketBehaviorIgnore, } err := dpdk.Init() require.NoError(t, err) err = dpdk.Start(nil) require.NoError(t, err) }) t.Run("when all values are valid, then no error should be returned", func(t *testing.T) { pathToSocket, socket := createSocketForTest(t, "") dpdk := Dpdk{ SocketPath: pathToSocket, DeviceTypes: []string{"ethdev"}, Log: testutil.Logger{}, } err := dpdk.Init() require.NoError(t, err) go simulateSocketResponse(socket, t) err = dpdk.Start(nil) require.NoError(t, err) }) } func TestMaintainConnections(t *testing.T) { t.Run("maintainConnections should return the error if socket doesn't exist", func(t *testing.T) { dpdk := Dpdk{ SocketPath: "/tmp/justrandompath", DeviceTypes: []string{"ethdev"}, Log: testutil.Logger{}, UnreachableSocketBehavior: unreachableSocketBehaviorError, } require.Empty(t, dpdk.connectors) err := dpdk.maintainConnections() defer dpdk.Stop() require.Error(t, err) require.Contains(t, err.Error(), "couldn't connect to socket") }) t.Run("maintainConnections should return the error if socket not found with dpdkPluginOptionInMemory", func(t *testing.T) { dpdk := Dpdk{ SocketPath: defaultPathToSocket, Log: testutil.Logger{}, PluginOptions: []string{dpdkPluginOptionInMemory}, UnreachableSocketBehavior: unreachableSocketBehaviorError, } var err error dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath) require.NoError(t, err) require.Empty(t, dpdk.connectors) err = dpdk.maintainConnections() require.Error(t, err) require.Contains(t, err.Error(), "no active sockets connections present") }) t.Run("maintainConnections shouldn't return error with 1 socket", func(t *testing.T) { pathToSocket, socket := createSocketForTest(t, "") dpdk := Dpdk{ SocketPath: pathToSocket, DeviceTypes: []string{"ethdev"}, Log: testutil.Logger{}, } go simulateSocketResponse(socket, t) require.Empty(t, dpdk.connectors) err := dpdk.maintainConnections() defer dpdk.Stop() require.NoError(t, err) require.Len(t, dpdk.connectors, 1) }) t.Run("maintainConnections shouldn't return error with multiple sockets", func(t *testing.T) { numSockets := rand.Intn(5) + 1 pathToSockets, sockets := createMultipleSocketsForTest(t, numSockets, "") dpdk := Dpdk{ SocketPath: pathToSockets[0], DeviceTypes: []string{"ethdev"}, Log: testutil.Logger{}, PluginOptions: []string{dpdkPluginOptionInMemory}, } var err error dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath) require.NoError(t, err) for _, socket := range sockets { go simulateSocketResponse(socket, t) } require.Empty(t, dpdk.connectors) err = dpdk.maintainConnections() defer dpdk.Stop() require.NoError(t, err) require.Len(t, dpdk.connectors, numSockets) }) t.Run("Test maintainConnections without dpdkPluginOptionInMemory option", func(t *testing.T) { pathToSocket, socket := createSocketForTest(t, "") dpdk := Dpdk{ SocketPath: pathToSocket, DeviceTypes: []string{"ethdev"}, Log: testutil.Logger{}, } go simulateSocketResponse(socket, t) require.Empty(t, dpdk.connectors) err := dpdk.maintainConnections() require.NoError(t, err) require.Len(t, dpdk.connectors, 1) dpdk.Stop() require.Empty(t, dpdk.connectors) }) t.Run("Test maintainConnections with dpdkPluginOptionInMemory option", func(t *testing.T) { pathToSocket1, socket1 := createSocketForTest(t, "") go simulateSocketResponse(socket1, t) dpdk := Dpdk{ SocketPath: pathToSocket1, DeviceTypes: []string{"ethdev"}, Log: testutil.Logger{}, PluginOptions: []string{dpdkPluginOptionInMemory}, } var err error dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath) require.NoError(t, err) require.Empty(t, dpdk.connectors) err = dpdk.maintainConnections() require.NoError(t, err) require.Len(t, dpdk.connectors, 1) // Adding 2 sockets more pathToSocket2, socket2 := createSocketForTest(t, filepath.Dir(pathToSocket1)) pathToSocket3, socket3 := createSocketForTest(t, filepath.Dir(pathToSocket1)) require.NotEqual(t, pathToSocket2, pathToSocket3) go simulateSocketResponse(socket2, t) go simulateSocketResponse(socket3, t) err = dpdk.maintainConnections() require.NoError(t, err) require.Len(t, dpdk.connectors, 3) // Close 2 new sockets socket2.Close() socket3.Close() err = dpdk.maintainConnections() require.NoError(t, err) require.Len(t, dpdk.connectors, 1) require.Equal(t, pathToSocket1, dpdk.connectors[0].pathToSocket) dpdk.Stop() require.Empty(t, dpdk.connectors) }) } func TestClose(t *testing.T) { t.Run("Num of connections should be 0 after Stop func", func(t *testing.T) { pathToSocket, socket := createSocketForTest(t, "") dpdk := Dpdk{ SocketPath: pathToSocket, DeviceTypes: []string{"ethdev"}, Log: testutil.Logger{}, } go simulateSocketResponse(socket, t) require.Empty(t, dpdk.connectors) err := dpdk.maintainConnections() require.NoError(t, err) require.Len(t, dpdk.connectors, 1) dpdk.Stop() require.Empty(t, dpdk.connectors) }) } func Test_validateAdditionalCommands(t *testing.T) { t.Run("when validating commands in correct format then no error should be returned", func(t *testing.T) { dpdk := Dpdk{ AdditionalCommands: []string{"/test", "/help"}, } err := dpdk.validateAdditionalCommands() require.NoError(t, err) }) t.Run("when validating command that doesn't begin with slash then error should be returned", func(t *testing.T) { dpdk := Dpdk{ AdditionalCommands: []string{ "/test", "commandWithoutSlash", }, } err := dpdk.validateAdditionalCommands() require.Error(t, err) require.Contains(t, err.Error(), "command should start with slash") }) t.Run("when validating long command (without parameters) then error should be returned", func(t *testing.T) { dpdk := Dpdk{ AdditionalCommands: []string{ "/test", "/" + strings.Repeat("a", maxCommandLength), }, } err := dpdk.validateAdditionalCommands() require.Error(t, err) require.Contains(t, err.Error(), "command is too long") }) t.Run("when validating long command (with params) then error should be returned", func(t *testing.T) { dpdk := Dpdk{ AdditionalCommands: []string{ "/test", "/," + strings.Repeat("a", maxCommandLengthWithParams), }, } err := dpdk.validateAdditionalCommands() require.Error(t, err) require.Contains(t, err.Error(), "shall be less than 1024 characters") }) t.Run("when validating empty command then error should be returned", func(t *testing.T) { dpdk := Dpdk{ AdditionalCommands: []string{ "/test", "", }, } err := dpdk.validateAdditionalCommands() require.Error(t, err) require.Contains(t, err.Error(), "got empty command") }) t.Run("when validating commands with duplicates then duplicates should be removed and no error should be returned", func(t *testing.T) { dpdk := Dpdk{ AdditionalCommands: []string{ "/test", "/test", }, } require.Len(t, dpdk.AdditionalCommands, 2) err := dpdk.validateAdditionalCommands() require.Len(t, dpdk.AdditionalCommands, 1) require.NoError(t, err) }) } func prepareEnvironment() (*mocks.Conn, Dpdk, *testutil.Accumulator) { mockConnection := &mocks.Conn{} dpdk := Dpdk{ connectors: []*dpdkConnector{{ connection: mockConnection, initMessage: &initMessage{ Version: "mockedDPDK", Pid: 1, MaxOutputLen: 1024, }, accessTimeout: 2 * time.Second, }}, Log: testutil.Logger{}, } mockAcc := &testutil.Accumulator{} return mockConnection, dpdk, mockAcc } func prepareEnvironmentWithMultiSockets() ([]*mocks.Conn, Dpdk, *testutil.Accumulator) { mockConnections := []*mocks.Conn{{}, {}} dpdk := Dpdk{ connectors: []*dpdkConnector{ { connection: mockConnections[0], initMessage: &initMessage{ Version: "mockedDPDK", Pid: 1, MaxOutputLen: 1024, }, accessTimeout: 2 * time.Second, }, { connection: mockConnections[1], initMessage: &initMessage{ Version: "mockedDPDK", Pid: 2, MaxOutputLen: 1024, }, accessTimeout: 2 * time.Second, }, }, Log: testutil.Logger{}, } mockAcc := &testutil.Accumulator{} return mockConnections, dpdk, mockAcc } func prepareEnvironmentWithInitializedMessage(initMsg *initMessage) (*mocks.Conn, Dpdk, *testutil.Accumulator) { mockConnection := &mocks.Conn{} dpdk := Dpdk{ connectors: []*dpdkConnector{{ connection: mockConnection, accessTimeout: 2 * time.Second, initMessage: initMsg, }}, Log: testutil.Logger{}, } mockAcc := &testutil.Accumulator{} return mockConnection, dpdk, mockAcc } func Test_appendCommandsWithParams(t *testing.T) { t.Run("when got valid data, then valid commands with params should be created", func(t *testing.T) { mockConn, dpdk, _ := prepareEnvironment() defer mockConn.AssertExpectations(t) response := `{"/testendpoint": [1,123]}` simulateResponse(mockConn, response, nil) expectedCommands := []string{"/action1,1", "/action1,123", "/action2,1", "/action2,123"} for _, dpdkConn := range dpdk.connectors { result, err := dpdkConn.appendCommandsWithParamsFromList("/testendpoint", []string{"/action1", "/action2"}) require.NoError(t, err) require.Len(t, result, 4) require.ElementsMatch(t, result, expectedCommands) } }) } func Test_getCommandsAndParamsCombinations(t *testing.T) { t.Run("when 2 ethdev commands are enabled, then 2*numberOfIds new commands should be appended", func(t *testing.T) { mockConn, dpdk, mockAcc := prepareEnvironment() defer mockConn.AssertExpectations(t) response := fmt.Sprintf(`{%q: [1, 123]}`, ethdevListCommand) simulateResponse(mockConn, response, nil) expectedCommands := []string{"/ethdev/stats,1", "/ethdev/stats,123", "/ethdev/xstats,1", "/ethdev/xstats,123"} dpdk.DeviceTypes = []string{"ethdev"} dpdk.ethdevCommands = []string{"/ethdev/stats", "/ethdev/xstats"} commands := dpdk.gatherCommands(mockAcc, dpdk.connectors[0]) require.ElementsMatch(t, commands, expectedCommands) require.Empty(t, mockAcc.Errors) }) t.Run("when 1 rawdev command is enabled, then 2*numberOfIds new commands should be appended", func(t *testing.T) { mockConn, dpdk, mockAcc := prepareEnvironment() defer mockConn.AssertExpectations(t) response := fmt.Sprintf(`{%q: [1, 123]}`, rawdevListCommand) simulateResponse(mockConn, response, nil) expectedCommands := []string{"/rawdev/xstats,1", "/rawdev/xstats,123"} dpdk.DeviceTypes = []string{"rawdev"} dpdk.rawdevCommands = []string{"/rawdev/xstats"} commands := dpdk.gatherCommands(mockAcc, dpdk.connectors[0]) require.ElementsMatch(t, commands, expectedCommands) require.Empty(t, mockAcc.Errors) }) t.Run("when 2 ethdev commands are enabled but one command is disabled, then numberOfIds new commands should be appended", func(t *testing.T) { mockConn, dpdk, mockAcc := prepareEnvironment() defer mockConn.AssertExpectations(t) response := fmt.Sprintf(`{%q: [1, 123]}`, ethdevListCommand) simulateResponse(mockConn, response, nil) expectedCommands := []string{"/ethdev/stats,1", "/ethdev/stats,123"} dpdk.DeviceTypes = []string{"ethdev"} dpdk.ethdevCommands = []string{"/ethdev/stats", "/ethdev/xstats"} var err error dpdk.ethdevExcludedCommandsFilter, err = filter.Compile([]string{"/ethdev/xstats"}) require.NoError(t, err) commands := dpdk.gatherCommands(mockAcc, dpdk.connectors[0]) require.ElementsMatch(t, commands, expectedCommands) require.Empty(t, mockAcc.Errors) }) t.Run("when ethdev commands are enabled but params fetching command returns error then error should be logged in accumulator", func(t *testing.T) { mockConn, dpdk, mockAcc := prepareEnvironment() defer mockConn.AssertExpectations(t) simulateResponse(mockConn, `{notAJson}`, errors.New("some error")) dpdk.DeviceTypes = []string{"ethdev"} dpdk.ethdevCommands = []string{"/ethdev/stats", "/ethdev/xstats"} commands := dpdk.gatherCommands(mockAcc, dpdk.connectors[0]) require.Empty(t, commands) require.Len(t, mockAcc.Errors, 1) }) } func Test_getDpdkInMemorySocketPaths(t *testing.T) { var err error t.Run("Should return nil if path doesn't exist", func(t *testing.T) { dpdk := Dpdk{ SocketPath: "/tmp/nothing-should-exist-here/test.socket", Log: testutil.Logger{}, } dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath) require.NoError(t, err) socketsPaths := dpdk.getDpdkInMemorySocketPaths() require.Nil(t, socketsPaths) }) t.Run("Should return nil if can't read the dir", func(t *testing.T) { dpdk := Dpdk{ SocketPath: "/root/no_access", Log: testutil.Logger{}, } dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath) require.NoError(t, err) socketsPaths := dpdk.getDpdkInMemorySocketPaths() require.Nil(t, socketsPaths) }) t.Run("Should return one socket from socket path", func(t *testing.T) { socketPath, _ := createSocketForTest(t, "") dpdk := Dpdk{ SocketPath: socketPath, Log: testutil.Logger{}, } dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath) require.NoError(t, err) socketsPaths := dpdk.getDpdkInMemorySocketPaths() require.Len(t, socketsPaths, 1) require.Equal(t, socketPath, socketsPaths[0]) }) t.Run("Should return 2 sockets from socket path", func(t *testing.T) { socketPaths, _ := createMultipleSocketsForTest(t, 2, "") dpdk := Dpdk{ SocketPath: socketPaths[0], Log: testutil.Logger{}, } dpdk.socketGlobPath, err = prepareGlob(dpdk.SocketPath) require.NoError(t, err) socketsPathsFromFunc := dpdk.getDpdkInMemorySocketPaths() require.Len(t, socketsPathsFromFunc, 2) require.Equal(t, socketPaths, socketsPathsFromFunc) }) } func Test_Gather(t *testing.T) { t.Run("Gather should return error, because socket weren't created", func(t *testing.T) { mockAcc := &testutil.Accumulator{} dpdk := Dpdk{ Log: testutil.Logger{}, PluginOptions: make([]string, 0), } require.NoError(t, dpdk.Init()) err := dpdk.Gather(mockAcc) require.Error(t, err) require.Contains(t, err.Error(), "couldn't connect to socket") }) t.Run("Gather shouldn't return error with UnreachableSocketBehavior: Ignore option, because socket weren't created", func(t *testing.T) { mockAcc := &testutil.Accumulator{} dpdk := Dpdk{ Log: testutil.Logger{}, UnreachableSocketBehavior: unreachableSocketBehaviorIgnore, } require.NoError(t, dpdk.Init()) err := dpdk.Gather(mockAcc) require.NoError(t, err) }) t.Run("When parsing a plain json without nested object, then its key should be equal to \"\"", func(t *testing.T) { mockConn, dpdk, mockAcc := prepareEnvironment() defer mockConn.AssertExpectations(t) dpdk.AdditionalCommands = []string{"/endpoint1"} simulateResponse(mockConn, `{"/endpoint1":"myvalue"}`, nil) err := dpdk.Gather(mockAcc) require.NoError(t, err) require.Empty(t, mockAcc.Errors) expected := []telegraf.Metric{ testutil.MustMetric( "dpdk", map[string]string{ "command": "/endpoint1", "params": "", }, map[string]interface{}{ "": "myvalue", }, time.Unix(0, 0), ), } actual := mockAcc.GetTelegrafMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) }) t.Run("When parsing a list of value in nested object then list should be flattened", func(t *testing.T) { mockConn, dpdk, mockAcc := prepareEnvironment() defer mockConn.AssertExpectations(t) dpdk.AdditionalCommands = []string{"/endpoint1"} simulateResponse(mockConn, `{"/endpoint1":{"myvalue":[0,1,123]}}`, nil) err := dpdk.Gather(mockAcc) require.NoError(t, err) require.Empty(t, mockAcc.Errors) expected := []telegraf.Metric{ testutil.MustMetric( "dpdk", map[string]string{ "command": "/endpoint1", "params": "", }, map[string]interface{}{ "myvalue_0": float64(0), "myvalue_1": float64(1), "myvalue_2": float64(123), }, time.Unix(0, 0), ), } actual := mockAcc.GetTelegrafMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) }) t.Run("Test Gather with Metadata Fields dpdk pid and version", func(t *testing.T) { testInitMessage := &initMessage{ Pid: 100, Version: "DPDK 21.11.11", MaxOutputLen: 1024, } mockConn, dpdk, mockAcc := prepareEnvironmentWithInitializedMessage(testInitMessage) dpdk.MetadataFields = []string{dpdkMetadataFieldPidName, dpdkMetadataFieldVersionName} defer mockConn.AssertExpectations(t) dpdk.AdditionalCommands = []string{"/endpoint1"} simulateResponse(mockConn, `{"/endpoint1":"myvalue"}`, nil) err := dpdk.Gather(mockAcc) require.NoError(t, err) require.Empty(t, mockAcc.Errors) expected := []telegraf.Metric{ testutil.MustMetric( "dpdk", map[string]string{ "command": "/endpoint1", "params": "", }, map[string]interface{}{ "": "myvalue", dpdkMetadataFieldPidName: testInitMessage.Pid, dpdkMetadataFieldVersionName: testInitMessage.Version, }, time.Unix(0, 0), ), } actual := mockAcc.GetTelegrafMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) }) t.Run("Test Gather with Metadata Fields dpdk_pid", func(t *testing.T) { testInitMessage := &initMessage{ Pid: 100, Version: "DPDK 21.11.11", MaxOutputLen: 1024, } mockConn, dpdk, mockAcc := prepareEnvironmentWithInitializedMessage(testInitMessage) dpdk.MetadataFields = []string{dpdkMetadataFieldPidName} defer mockConn.AssertExpectations(t) dpdk.AdditionalCommands = []string{"/endpoint1"} simulateResponse(mockConn, `{"/endpoint1":"myvalue"}`, nil) err := dpdk.Gather(mockAcc) require.NoError(t, err) require.Empty(t, mockAcc.Errors) expected := []telegraf.Metric{ testutil.MustMetric( "dpdk", map[string]string{ "command": "/endpoint1", "params": "", }, map[string]interface{}{ "": "myvalue", dpdkMetadataFieldPidName: testInitMessage.Pid, }, time.Unix(0, 0), ), } actual := mockAcc.GetTelegrafMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) }) t.Run("Test Gather without Metadata Fields", func(t *testing.T) { testInitMessage := &initMessage{ Pid: 100, Version: "DPDK 21.11.11", MaxOutputLen: 1024, } mockConn, dpdk, mockAcc := prepareEnvironmentWithInitializedMessage(testInitMessage) defer mockConn.AssertExpectations(t) dpdk.AdditionalCommands = []string{"/endpoint1"} simulateResponse(mockConn, `{"/endpoint1":"myvalue"}`, nil) err := dpdk.Gather(mockAcc) require.NoError(t, err) require.Empty(t, mockAcc.Errors) expected := []telegraf.Metric{ testutil.MustMetric( "dpdk", map[string]string{ "command": "/endpoint1", "params": "", }, map[string]interface{}{ "": "myvalue", }, time.Unix(0, 0), ), } actual := mockAcc.GetTelegrafMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) }) } func Test_Gather_MultiSocket(t *testing.T) { t.Run("Test Gather without Metadata Fields", func(t *testing.T) { mockConns, dpdk, mockAcc := prepareEnvironmentWithMultiSockets() defer func() { for _, mockConn := range mockConns { mockConn.AssertExpectations(t) } }() dpdk.AdditionalCommands = []string{"/endpoint1"} for _, mockConn := range mockConns { simulateResponse(mockConn, `{"/endpoint1":"myvalue"}`, nil) } err := dpdk.Gather(mockAcc) require.NoError(t, err) require.Empty(t, mockAcc.Errors) expected := []telegraf.Metric{ testutil.MustMetric( "dpdk", map[string]string{ "command": "/endpoint1", "params": "", }, map[string]interface{}{ "": "myvalue", }, time.Unix(0, 0), ), testutil.MustMetric( "dpdk", map[string]string{ "command": "/endpoint1", "params": "", }, map[string]interface{}{ "": "myvalue", }, time.Unix(0, 0), ), } actual := mockAcc.GetTelegrafMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) }) } func simulateResponse(mockConn *mocks.Conn, response string, readErr error) { mockConn.On("Write", mock.Anything).Return(0, nil) mockConn.On("Read", mock.Anything).Run(func(arg mock.Arguments) { elem := arg.Get(0).([]byte) copy(elem, response) }).Return(len(response), readErr) mockConn.On("SetDeadline", mock.Anything).Return(nil) if readErr != nil { mockConn.On("Close").Return(nil) } } func createSocketForTest(t *testing.T, dirPath string) (string, net.Listener) { var err error var pathToSocket string if len(dirPath) == 0 { // The Maximum length of the socket path is 104/108 characters, path created with t.TempDir() is too long for some cases // (it combines test name with subtest name and some random numbers in the path). Therefore, in this case, it is safer to stick with `os.MkdirTemp()`. //nolint:usetesting // Ignore "os.MkdirTemp() could be replaced by t.TempDir() in createSocketForTest" finding. dirPath, err = os.MkdirTemp("", "dpdk-test-socket") require.NoError(t, err) pathToSocket = filepath.Join(dirPath, dpdkSocketTemplateName) } else { // Create a socket in provided dirPath without duplication (similar to os.CreateTemp without creating a file) try := 1 for { pathToSocket = fmt.Sprintf("%s:%d", filepath.Join(dirPath, dpdkSocketTemplateName), try) if _, err = os.Stat(pathToSocket); err == nil { if try++; try < 1000 { continue } t.Fatalf("Can't create a temporary file for socket") } require.ErrorIs(t, err, os.ErrNotExist) break } } socket, err := net.Listen("unixpacket", pathToSocket) require.NoError(t, err) t.Cleanup(func() { socket.Close() os.RemoveAll(dirPath) }) return pathToSocket, socket } func createMultipleSocketsForTest(t *testing.T, numSockets int, dirPath string) (socketsPaths []string, sockets []net.Listener) { for i := 0; i < numSockets; i++ { pathToSocket, socket := createSocketForTest(t, dirPath) dirPath = filepath.Dir(pathToSocket) socketsPaths = append(socketsPaths, pathToSocket) sockets = append(sockets, socket) } return socketsPaths, sockets } func simulateSocketResponse(socket net.Listener, t *testing.T) { conn, err := socket.Accept() if err != nil { t.Error(err) return } initMessage, err := json.Marshal(initMessage{MaxOutputLen: 1}) if err != nil { t.Error(err) return } if _, err = conn.Write(initMessage); err != nil { t.Error(err) return } } func prepareGlob(path string) (*globpath.GlobPath, error) { return globpath.Compile(path + "*") }