//go:build linux // +build linux package intel_dlb import ( "encoding/json" "errors" "fmt" "net" "os" "testing" "time" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/inputs/dpdk/mocks" "github.com/influxdata/telegraf/testutil" ) func TestDLB_Init(t *testing.T) { t.Run("when SocketPath is empty, then set default value", func(t *testing.T) { dlb := IntelDLB{ SocketPath: "", Log: testutil.Logger{}, } require.Empty(t, dlb.SocketPath) //nolint:errcheck // we are just testing that socket path gets set to default, not that default is valid dlb.Init() require.Equal(t, defaultSocketPath, dlb.SocketPath) }) t.Run("invalid socket path throws error in Init method when UnreachableSocketBehavior is set to 'error'", func(t *testing.T) { dlb := IntelDLB{ SocketPath: "/this/is/wrong/path", Log: testutil.Logger{}, UnreachableSocketBehavior: "error", } err := dlb.Init() require.Error(t, err) require.Contains(t, err.Error(), "provided path does not exist") }) t.Run("not-existing socket path doesn't throw error in Init method when UnreachableSocketBehavior is set to 'ignore'", func(t *testing.T) { dlb := IntelDLB{ SocketPath: "/socket/is/not/there/yet", Log: testutil.Logger{}, UnreachableSocketBehavior: "ignore", } err := dlb.Init() require.Error(t, err) require.NotContains(t, err.Error(), "provided path does not exist") }) t.Run("wrong UnreachableSocketBehavior option throws error in Init method", func(t *testing.T) { pathToSocket, socket := createSocketForTest(t) defer socket.Close() dlb := IntelDLB{ SocketPath: pathToSocket, UnreachableSocketBehavior: "DAS BOOT", Log: testutil.Logger{}, } err := dlb.Init() require.Error(t, err) require.Contains(t, err.Error(), "unreachable_socket_behavior: unknown choice DAS BOOT") }) t.Run("wrong eventdev command throws error in Init method", func(t *testing.T) { pathToSocket, socket := createSocketForTest(t) defer socket.Close() dlb := IntelDLB{ SocketPath: pathToSocket, EventdevCommands: []string{"/noteventdev/dev_xstats"}, Log: testutil.Logger{}, } err := dlb.Init() require.Error(t, err) require.Contains(t, err.Error(), "provided command is not valid - ") }) t.Run("wrong eventdev command throws error", func(t *testing.T) { dlb := IntelDLB{ EventdevCommands: []string{"/noteventdev/dev_xstats"}, } err := validateEventdevCommands(dlb.EventdevCommands) require.Error(t, err) require.Contains(t, err.Error(), "provided command is not valid - ") }) t.Run("validate eventdev command", func(t *testing.T) { dlb := IntelDLB{ EventdevCommands: []string{"/eventdev/dev_xstats"}, } err := validateEventdevCommands(dlb.EventdevCommands) require.NoError(t, err) }) t.Run("successfully initialize intel_dlb struct", func(t *testing.T) { pathToSocket, socket := createSocketForTest(t) fileMock := &mockRasReader{} defer socket.Close() dlb := IntelDLB{ SocketPath: pathToSocket, Log: testutil.Logger{}, rasReader: fileMock, } const globPath = "/sys/devices/pci0000:00/0000:00:00.0/device" fileMock.On("gatherPaths", mock.Anything).Return([]string{globPath}, nil).Once(). On("readFromFile", mock.Anything).Return([]byte("0x2710"), nil).Once() err := dlb.Init() require.NoError(t, err) require.Equal(t, []string{"/eventdev/dev_xstats", "/eventdev/port_xstats", "/eventdev/queue_xstats", "/eventdev/queue_links"}, dlb.EventdevCommands) fileMock.AssertExpectations(t) }) t.Run("throw error while initializing dlb plugin when theres no dlb device", func(t *testing.T) { fileMock := &mockRasReader{} pathToSocket, socket := createSocketForTest(t) defer socket.Close() dlb := IntelDLB{ rasReader: fileMock, SocketPath: pathToSocket, Log: testutil.Logger{}, } const emptyPath = "" fileMock.On("gatherPaths", mock.Anything).Return([]string{emptyPath}, errors.New("can't find device folder")).Once() err := dlb.Init() require.Error(t, err) require.Contains(t, err.Error(), "can't find device folder") fileMock.AssertExpectations(t) }) } func TestDLB_writeReadSocketMessage(t *testing.T) { t.Run("throws custom error message when write error occur", func(t *testing.T) { mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, } mockConn.On("Write", make([]byte, 0)).Return(0, errors.New("write error")).Once(). On("Close").Return(nil).Once() _, _, err := dlb.writeReadSocketMessage("") require.Error(t, err) require.Contains(t, err.Error(), "failed to send command to socket: 'write error'") mockConn.AssertExpectations(t) }) t.Run("throws custom error message when read error occur", func(t *testing.T) { mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, } simulateResponse(mockConn, "", errors.New("read error")) _, _, err := dlb.writeReadSocketMessage("") require.Error(t, err) require.Contains(t, err.Error(), "failed to read response of from socket: 'read error'") mockConn.AssertExpectations(t) }) t.Run("throws custom error message when write error occur", func(t *testing.T) { mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, } mockConn.On("Write", make([]byte, 0)).Return(0, nil).Once(). On("Read", mock.Anything).Return(0, nil). On("Close").Return(nil).Once() _, _, err := dlb.writeReadSocketMessage("") require.Error(t, err) require.Contains(t, err.Error(), "got empty response from socket: 'message length is empty'") mockConn.AssertExpectations(t) }) } func TestDLB_parseJSON(t *testing.T) { var tests = []struct { testName string socketReply []byte replyMsgLen int errMsg string }{ {"wrong json format", []byte("/wrong/json"), 10, "invalid character '/' looking for beginning of value"}, {"socket reply length equal to 0 throws error", []byte("/wrong/json"), 0, "socket reply message is empty"}, {"invalid reply length throws error", []byte("/wrong/json"), 20, "socket reply length is bigger than it should be"}, {"nil socket reply throws error", nil, 0, "socket reply is empty"}, } for _, testCase := range tests { t.Run(testCase.testName, func(t *testing.T) { mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, } mockConn.On("Close").Return(nil).Once() err := dlb.parseJSON(testCase.replyMsgLen, testCase.socketReply, make(map[string]interface{})) require.Error(t, err) require.Contains(t, err.Error(), testCase.errMsg) mockConn.AssertExpectations(t) }) } } func TestDLB_getInitMessageLength(t *testing.T) { t.Run("trying to unmarshal invalid JSON throws error", func(t *testing.T) { fileMock := &mockRasReader{} mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, rasReader: fileMock, } mockConn.On("Read", mock.Anything).Run(func(arg mock.Arguments) { elem := arg.Get(0).([]byte) copy(elem, "") }).Return(len(""), nil).Once().On("Close").Return(nil).Once() err := dlb.setInitMessageLength() require.Error(t, err) require.Contains(t, err.Error(), "failed to parse json") fileMock.AssertExpectations(t) }) t.Run("when init message equals 0 throw error", func(t *testing.T) { fileMock := &mockRasReader{} mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, rasReader: fileMock, } dlb.maxInitMessageLength = 1024 const initMsgResponse = "{\"version\":\"DPDK 20.11.3\",\"pid\":208361,\"max_output_len\":0}" mockConn.On("Read", mock.Anything).Run(func(arg mock.Arguments) { elem := arg.Get(0).([]byte) copy(elem, initMsgResponse) }).Return(len(initMsgResponse), nil).Once().On("Close").Return(nil).Once() err := dlb.setInitMessageLength() require.Error(t, err) require.Contains(t, err.Error(), "got empty response from socket") fileMock.AssertExpectations(t) }) } func TestDLB_gatherCommandsResult(t *testing.T) { t.Run("trying connecting to wrong socket throw error", func(t *testing.T) { pathToSocket := "/tmp/dpdk-test-socket" socket, err := net.Listen("unix", pathToSocket) fileMock := &mockRasReader{} defer socket.Close() dlb := IntelDLB{ SocketPath: pathToSocket, Log: testutil.Logger{}, rasReader: fileMock, } require.NoError(t, err) err = dlb.gatherCommandsResult("", nil) require.Error(t, err) require.Contains(t, err.Error(), "connect: protocol wrong type for socket") fileMock.AssertExpectations(t) }) } func TestDLB_gatherCommandsWithDeviceIndex(t *testing.T) { t.Run("process wrong commands should throw error", func(t *testing.T) { mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, EventdevCommands: []string{"/eventdev/dev_xstats"}, } response := "/wrong/JSON" dlb.maxInitMessageLength = 1024 simulateResponse(mockConn, response, nil) mockConn.On("Write", mock.Anything).Return(0, nil) mockConn.On("Close").Return(nil).Once() _, err := dlb.gatherCommandsWithDeviceIndex() require.Error(t, err) require.Contains(t, err.Error(), "failed to parse json") mockConn.AssertExpectations(t) }) t.Run("process commands should return array with command and device id", func(t *testing.T) { mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, maxInitMessageLength: 1024, EventdevCommands: []string{"/eventdev/dev_xstats"}, } response := fmt.Sprintf(`{%q: [0, 1]}`, eventdevListCommand) simulateResponse(mockConn, response, nil) expectedCommands := []string{"/eventdev/dev_xstats,0", "/eventdev/dev_xstats,1"} commands, err := dlb.gatherCommandsWithDeviceIndex() require.NoError(t, err) require.Equal(t, expectedCommands, commands) mockConn.AssertExpectations(t) }) t.Run("process commands should return array with queue and device id", func(t *testing.T) { mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, maxInitMessageLength: 1024, EventdevCommands: []string{"/eventdev/queue_links"}, } responseDevList := fmt.Sprintf(`{%q: [0]}`, eventdevListCommand) simulateResponse(mockConn, responseDevList, nil) responseQueueLinks := `{"0": [0]}` simulateResponse(mockConn, responseQueueLinks, nil) expectedCommands := []string{"/eventdev/queue_links,0,0"} commands, err := dlb.gatherCommandsWithDeviceIndex() require.NoError(t, err) require.Equal(t, expectedCommands, commands) mockConn.AssertExpectations(t) }) t.Run("process wrong commands should throw error", func(t *testing.T) { mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, maxInitMessageLength: 1024, EventdevCommands: []string{"/eventdev/dev_xstats", "/eventdev/wrong"}, } response := fmt.Sprintf(`{%q: [0, 1]}`, eventdevListCommand) mockConn.On("Write", mock.Anything).Return(0, nil).Once() mockConn.On("Read", mock.Anything).Run(func(arg mock.Arguments) { elem := arg.Get(0).([]byte) copy(elem, response) }).Return(len(response), nil).Once().On("Close").Return(nil).Once() _, err := dlb.gatherCommandsWithDeviceIndex() require.Error(t, err) require.Contains(t, err.Error(), "cannot split command") mockConn.AssertExpectations(t) }) } func TestDLB_gatherSecondDeviceIndex(t *testing.T) { t.Run("process wrong commands should return error", func(t *testing.T) { mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, EventdevCommands: []string{"/eventdev/wrong"}, } mockConn.On("Close").Return(nil).Once() _, err := dlb.gatherSecondDeviceIndex(0, dlb.EventdevCommands[0]) require.Error(t, err) require.Contains(t, err.Error(), "cannot split command -") mockConn.AssertExpectations(t) }) t.Run("process wrong response commands should throw error", func(t *testing.T) { mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, EventdevCommands: []string{"/eventdev/port_xstats"}, } response := "/wrong/JSON" simulateResponse(mockConn, response, nil) mockConn.On("Close").Return(nil).Once() _, err := dlb.gatherSecondDeviceIndex(0, dlb.EventdevCommands[0]) require.Error(t, err) require.Contains(t, err.Error(), "failed to parse json") mockConn.AssertExpectations(t) }) t.Run("process wrong response commands should throw error and close socket, after second function call should connect to socket", func(t *testing.T) { mockConn := &mocks.Conn{} pathToSocket, socket := createSocketForTest(t) defer socket.Close() dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, maxInitMessageLength: 1024, EventdevCommands: []string{"/eventdev/port_xstats"}, } response := "/wrong/JSON" simulateResponse(mockConn, response, nil) mockConn.On("Close").Return(nil).Once() _, err := dlb.gatherSecondDeviceIndex(0, dlb.EventdevCommands[0]) require.Nil(t, dlb.connection) require.Error(t, err) require.Contains(t, err.Error(), "failed to parse json") dlb.SocketPath = pathToSocket go simulateSocketResponseForGather(socket, t) commandDeviceIndexes, err := dlb.gatherSecondDeviceIndex(0, dlb.EventdevCommands[0]) require.NoError(t, err) expectedCommands := []string{"/eventdev/port_xstats,0,0", "/eventdev/port_xstats,0,1"} commands := commandDeviceIndexes require.Equal(t, expectedCommands, commands) mockConn.AssertExpectations(t) }) t.Run("process commands should return array with command and second device id", func(t *testing.T) { mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, maxInitMessageLength: 1024, EventdevCommands: []string{"/eventdev/port_xstats"}, } eventdevListWithSecondIndex := []string{"/eventdev/port_list", "/eventdev/queue_list"} response := fmt.Sprintf(`{%q: [0, 1]}`, eventdevListWithSecondIndex[0]) simulateResponse(mockConn, response, nil) expectedCommands := []string{"/eventdev/port_xstats,0,0", "/eventdev/port_xstats,0,1"} commandDeviceIndexes, err := dlb.gatherSecondDeviceIndex(0, dlb.EventdevCommands[0]) commands := commandDeviceIndexes require.NoError(t, err) require.Equal(t, expectedCommands, commands) mockConn.AssertExpectations(t) }) } func TestDLB_processCommandResult(t *testing.T) { t.Run("gather xstats info with valid values", func(t *testing.T) { mockAcc := &testutil.Accumulator{} mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, maxInitMessageLength: 1024, EventdevCommands: []string{"/eventdev/dev_xstats"}, } response := fmt.Sprintf(`{%q: [0]}`, eventdevListCommand) simulateResponse(mockConn, response, nil) response = `{"/eventdev/dev_xstats": {"dev_rx_ok": 0}}` simulateResponse(mockConn, response, nil) err := dlb.gatherMetricsFromSocket(mockAcc) require.NoError(t, err) expected := []telegraf.Metric{ testutil.MustMetric( "intel_dlb", map[string]string{ "command": "/eventdev/dev_xstats,0", }, map[string]interface{}{ "dev_rx_ok": int64(0), }, time.Unix(0, 0), ), } actual := mockAcc.GetTelegrafMetrics() testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime()) mockConn.AssertExpectations(t) }) t.Run("successfully gather xstats and aer metrics", func(t *testing.T) { mockAcc := &testutil.Accumulator{} mockConn := &mocks.Conn{} fileMock := &mockRasReader{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, EventdevCommands: []string{"/eventdev/dev_xstats"}, devicesDir: []string{"/sys/devices/pci0000:00/0000:00:00.0/device"}, rasReader: fileMock, maxInitMessageLength: 1024, } responseGather := fmt.Sprintf(`{%q: [0]}`, eventdevListCommand) mockConn.On("Write", mock.Anything).Return(0, nil).Twice() mockConn.On("Read", mock.Anything).Run(func(arg mock.Arguments) { elem := arg.Get(0).([]byte) copy(elem, responseGather) }).Return(len(responseGather), nil).Once() response := `{"/eventdev/dev_xstats": {"dev_rx_ok": 0}}` mockConn.On("Read", mock.Anything).Run(func(arg mock.Arguments) { elem := arg.Get(0).([]byte) copy(elem, response) }).Return(len(response), nil).Once() fileMock.On("readFromFile", mock.AnythingOfType("string")).Return([]byte(aerCorrectableData), nil).Once(). On("readFromFile", mock.AnythingOfType("string")).Return([]byte(aerFatalData), nil).Once(). On("readFromFile", mock.AnythingOfType("string")).Return([]byte(aerNonFatalData), nil).Once() err := dlb.Gather(mockAcc) require.NoError(t, err) actual := mockAcc.GetTelegrafMetrics() testutil.SortMetrics() ex := expectedTelegrafMetrics testutil.RequireMetricsEqual(t, ex, actual, testutil.IgnoreTime()) mockConn.AssertExpectations(t) }) t.Run("invalid JSON throws error in process result function", func(t *testing.T) { mockAcc := &testutil.Accumulator{} mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, EventdevCommands: []string{"/eventdev/dev_xstats"}, } response := fmt.Sprintf(`{%q: [0]}`, eventdevListCommand) simulateResponse(mockConn, response, nil) simulateResponse(mockConn, "/wrong/json", nil) mockConn.On("Close").Return(nil).Once() err := dlb.gatherMetricsFromSocket(mockAcc) require.Error(t, err) require.Contains(t, err.Error(), "failed to parse json") mockConn.AssertExpectations(t) }) t.Run("throw error when reply message is empty", func(t *testing.T) { mockAcc := &testutil.Accumulator{} mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, } const response = "" 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), nil).Once() mockConn.On("Close").Return(nil) err := dlb.gatherMetricsFromSocket(mockAcc) require.Error(t, err) require.Contains(t, err.Error(), "got empty response from socket") mockConn.AssertExpectations(t) }) t.Run("throw error when can't read socket reply", func(t *testing.T) { mockAcc := &testutil.Accumulator{} mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, } const response = "" 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), errors.New("read error")).Once() mockConn.On("Close").Return(nil) err := dlb.gatherMetricsFromSocket(mockAcc) require.Error(t, err) require.Contains(t, err.Error(), "failed to read response of from socket") mockConn.AssertExpectations(t) }) t.Run("throw error when invalid reply was provided", func(t *testing.T) { mockAcc := &testutil.Accumulator{} mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, maxInitMessageLength: 1024, Log: testutil.Logger{}, } simulateResponse(mockConn, "\"string reply\"", nil) mockConn.On("Close").Return(nil).Once() err := dlb.gatherMetricsFromSocket(mockAcc) require.Error(t, err) require.Contains(t, err.Error(), "json: cannot unmarshal string into Go value of type") mockConn.AssertExpectations(t) }) t.Run("throw error while processing xstats", func(t *testing.T) { mockAcc := &testutil.Accumulator{} mockConn := &mocks.Conn{} fileMock := &mockRasReader{} dlb := IntelDLB{ connection: mockConn, Log: testutil.Logger{}, EventdevCommands: []string{"/eventdev/dev_xstats"}, rasReader: fileMock, maxInitMessageLength: 1024, } mockConn.On("Close").Return(nil) responseGather := fmt.Sprintf(`{%q: [0]}`, eventdevListCommand) mockConn.On("Write", mock.Anything).Return(0, nil).Once(). On("Read", mock.Anything).Run(func(arg mock.Arguments) { elem := arg.Get(0).([]byte) copy(elem, responseGather) }).Return(len(responseGather), nil).Once() wrongResponse := "/wrong/json" mockConn.On("Write", mock.Anything).Return(0, nil).Once(). On("Read", mock.Anything).Run(func(arg mock.Arguments) { elem := arg.Get(0).([]byte) copy(elem, wrongResponse) }).Return(len(wrongResponse), nil).Once() err := dlb.gatherMetricsFromSocket(mockAcc) require.Error(t, err) require.Contains(t, err.Error(), "failed to parse json:") mockConn.AssertExpectations(t) }) } func Test_checkAndAddDLBDevice(t *testing.T) { t.Run("throw error when dlb validation can't find device folder", func(t *testing.T) { fileMock := &mockRasReader{} dlb := IntelDLB{ rasReader: fileMock, Log: testutil.Logger{}, } fileMock.On("gatherPaths", mock.AnythingOfType("string")).Return(nil, errors.New("can't find device folder")).Once() err := dlb.checkAndAddDLBDevice() require.Error(t, err) require.Contains(t, err.Error(), "can't find device folder") fileMock.AssertExpectations(t) }) t.Run("reading file throws error", func(t *testing.T) { fileMock := &mockRasReader{} dlb := IntelDLB{ rasReader: fileMock, Log: testutil.Logger{}, devicesDir: []string{"/sys/devices/pci0000:00/0000:00:00.0/device"}, } const globPath = "/sys/devices/pci0000:00/0000:00:00.0/device" fileMock.On("gatherPaths", mock.Anything).Return([]string{globPath}, nil).Once(). On("readFromFile", mock.Anything).Return([]byte("0x2710"), errors.New("read error while getting device folders")).Once() err := dlb.checkAndAddDLBDevice() require.Error(t, err) require.Contains(t, err.Error(), "read error while getting device folders") fileMock.AssertExpectations(t) }) t.Run("reading file with empty rasreader throws error", func(t *testing.T) { fileMock := &mockRasReader{} dlb := IntelDLB{ Log: testutil.Logger{}, } err := dlb.checkAndAddDLBDevice() require.Error(t, err) require.Contains(t, err.Error(), "rasreader was not initialized") fileMock.AssertExpectations(t) }) t.Run("reading file with unused device IDs throws error", func(t *testing.T) { fileMock := &mockRasReader{} dlb := IntelDLB{ rasReader: fileMock, Log: testutil.Logger{}, devicesDir: []string{"/sys/devices/pci0000:00/0000:00:00.0/device"}, DLBDeviceIDs: []string{"0x2710"}, } const globPath = "/sys/devices/pci0000:00/0000:00:00.0/device" fileMock.On("gatherPaths", mock.Anything).Return([]string{globPath}, nil).Once(). On("readFromFile", mock.Anything).Return([]byte("0x2710"), errors.New("read error while getting device folders")).Once() err := dlb.checkAndAddDLBDevice() require.Error(t, err) require.Contains(t, err.Error(), "read error while getting device folders") fileMock.AssertExpectations(t) }) t.Run("no errors when dlb device was found while validating", func(t *testing.T) { fileMock := &mockRasReader{} dlb := IntelDLB{ rasReader: fileMock, Log: testutil.Logger{}, DLBDeviceIDs: []string{"0x2710"}, } const globPath = "/sys/devices/pci0000:00/0000:00:00.0/device" fileMock.On("gatherPaths", mock.Anything).Return([]string{globPath}, nil).Once(). On("readFromFile", mock.Anything).Return([]byte("0x2710"), nil).Once() err := dlb.checkAndAddDLBDevice() require.NoError(t, err) expected := []string{"/sys/devices/pci0000:00/0000:00:00.0"} require.Equal(t, expected, dlb.devicesDir) fileMock.AssertExpectations(t) }) t.Run("no errors when found unused dlb device", func(t *testing.T) { fileMock := &mockRasReader{} dlb := IntelDLB{ rasReader: fileMock, Log: testutil.Logger{}, DLBDeviceIDs: []string{"0x2710", "0x0000"}, } const globPath = "/sys/devices/pci0000:00/0000:00:00.0/device" fileMock.On("gatherPaths", mock.Anything).Return([]string{globPath}, nil).Once(). On("readFromFile", mock.Anything).Return([]byte("0x2710"), nil).Once() err := dlb.checkAndAddDLBDevice() require.NoError(t, err) expected := []string{"/sys/devices/pci0000:00/0000:00:00.0"} require.Equal(t, expected, dlb.devicesDir) fileMock.AssertExpectations(t) }) t.Run("error when dlb device was not found while validating", func(t *testing.T) { fileMock := &mockRasReader{} mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, rasReader: fileMock, Log: testutil.Logger{}, } const globPath = "/sys/devices/pci0000:00/0000:00:00.0/device" fileMock.On("gatherPaths", mock.Anything).Return([]string{globPath}, nil).Once(). On("readFromFile", mock.Anything).Return([]byte("0x7100"), nil).Once() err := dlb.checkAndAddDLBDevice() require.Error(t, err) require.Contains(t, err.Error(), fmt.Sprintf("cannot find any of provided IDs on the system - %+q", dlb.DLBDeviceIDs)) fileMock.AssertExpectations(t) mockConn.AssertExpectations(t) }) } func Test_readRasMetrics(t *testing.T) { var errorTests = []struct { name string returnResponse []byte err error errMsg string }{ {"error when reading fails", []byte(aerCorrectableData), errors.New("read error"), "read error"}, {"error when empty data is given", []byte(""), nil, "no value to parse"}, {"error when trying to split empty data", []byte("x1 x2"), nil, "failed to parse value"}, } for _, test := range errorTests { t.Run(test.name, func(t *testing.T) { fileMock := &mockRasReader{} mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, rasReader: fileMock, Log: testutil.Logger{}, } mockConn.On("Close").Return(nil).Once() fileMock.On("readFromFile", mock.AnythingOfType("string")).Return(test.returnResponse, test.err).Once() _, err := dlb.readRasMetrics("/dlb", "device") require.Error(t, err) require.Contains(t, err.Error(), test.errMsg) fileMock.AssertExpectations(t) }) } t.Run("no error when reading countable error file", func(t *testing.T) { fileMock := &mockRasReader{} dlb := IntelDLB{ rasReader: fileMock, Log: testutil.Logger{}, } fileMock.On("readFromFile", mock.AnythingOfType("string")).Return([]byte(aerCorrectableData), nil).Once() _, err := dlb.readRasMetrics("/dlb", "device") require.NoError(t, err) fileMock.AssertExpectations(t) }) } func Test_gatherRasMetrics(t *testing.T) { var errorTests = []struct { name string returnResponse []byte err error errMsg string }{ {"throw error when data in file is invalid", nil, nil, "no value to parse"}, {"throw error when data in file is invalid", []byte("x1 x2"), nil, "failed to parse value"}, } for _, test := range errorTests { t.Run(test.name, func(t *testing.T) { fileMock := &mockRasReader{} mockAcc := &testutil.Accumulator{} mockConn := &mocks.Conn{} dlb := IntelDLB{ connection: mockConn, rasReader: fileMock, devicesDir: []string{"/sys/devices/pci0000:00/0000:00:00.0/device"}, Log: testutil.Logger{}, } mockConn.On("Close").Return(nil).Once() fileMock.On("readFromFile", mock.AnythingOfType("string")).Return(test.returnResponse, test.err).Once() err := dlb.gatherRasMetrics(mockAcc) require.Error(t, err) require.Contains(t, err.Error(), test.errMsg) fileMock.AssertExpectations(t) }) } t.Run("gather ras metrics and add to accumulator", func(t *testing.T) { fileMock := &mockRasReader{} mockAcc := &testutil.Accumulator{} dlb := IntelDLB{ rasReader: fileMock, devicesDir: []string{"/sys/devices/pci0000:00/0000:00:00.0/device"}, Log: testutil.Logger{}, } fileMock.On("readFromFile", mock.AnythingOfType("string")).Return([]byte(aerCorrectableData), nil).Once(). On("readFromFile", mock.AnythingOfType("string")).Return([]byte(aerFatalData), nil).Once(). On("readFromFile", mock.AnythingOfType("string")).Return([]byte(aerNonFatalData), nil).Once() err := dlb.gatherRasMetrics(mockAcc) require.NoError(t, err) actual := mockAcc.GetTelegrafMetrics() testutil.SortMetrics() testutil.RequireMetricsEqual(t, expectedRasMetrics, actual, testutil.IgnoreTime()) fileMock.AssertExpectations(t) }) } func Test_rasReader(t *testing.T) { file := rasReaderImpl{} // Create unique temporary file fileobj, err := os.CreateTemp(t.TempDir(), "qat") require.NoError(t, err) t.Run("tests with existing file", func(t *testing.T) { // Remove the temporary file after this test defer os.Remove(fileobj.Name()) _, err = fileobj.WriteString(testFileContent) require.NoError(t, err) err = fileobj.Close() require.NoError(t, err) // Check that content returned by read is equal to provided file. data, err := file.readFromFile(fileobj.Name()) require.NoError(t, err) require.Equal(t, []byte(testFileContent), data) // Error if path is malformed. _, err = file.readFromFile(fileobj.Name() + "/../..") require.Error(t, err) require.Contains(t, err.Error(), "not a directory") }) var errorTests = []struct { name string filePath string expectedErrMsg string }{ {"error if file does not exist", fileobj.Name(), "no such file or directory"}, {"error if path does not point to regular file", t.TempDir(), "is a directory"}, {"error if file does not exist", "/not/path/unreal/path", "no such file or directory"}, } for _, test := range errorTests { t.Run(test.name, func(t *testing.T) { _, err = file.readFromFile(test.filePath) require.Error(t, err) require.Contains(t, err.Error(), test.expectedErrMsg) }) } } func simulateResponse(mockConn *mocks.Conn, response string, readErr error) { mockConn.On("Write", mock.Anything).Return(0, nil).Once(). On("Read", mock.Anything).Run(func(arg mock.Arguments) { elem := arg.Get(0).([]byte) copy(elem, response) }).Return(len(response), readErr).Once() if readErr != nil { mockConn.On("Close").Return(nil).Once() } } func simulateSocketResponseForGather(socket net.Listener, t *testing.T) { conn, err := socket.Accept() if err != nil { t.Error(err) return } type initMessage struct { Version string `json:"version"` Pid int `json:"pid"` MaxOutputLen uint32 `json:"max_output_len"` } initMsg, err := json.Marshal(initMessage{ Version: "", Pid: 1, MaxOutputLen: 1024, }) if err != nil { t.Error(err) return } if _, err = conn.Write(initMsg); err != nil { t.Error(err) return } eventdevListWithSecondIndex := []string{"/eventdev/port_list", "/eventdev/queue_list"} if _, err = fmt.Fprintf(conn, `{%q: [0, 1]}`, eventdevListWithSecondIndex[0]); err != nil { t.Error(err) return } } func createSocketForTest(t *testing.T) (string, net.Listener) { pathToSocket := "/tmp/dpdk-test-socket" socket, err := net.Listen("unixpacket", pathToSocket) require.NoError(t, err) return pathToSocket, socket } const ( testFileContent = ` line1 line2 2 line3 line4 line5 ` aerCorrectableData = ` RxErr 1 BadTLP 0 BadDLLP 0 Rollover 1 Timeout 0 NonFatalErr 0 CorrIntErr 0 HeaderOF 0 TOTAL_ERR_COR 0` aerFatalData = ` Undefined 0 DLP 1 SDES 0 TLP 0 FCP 0 CmpltTO 0 CmpltAbrt 0 UnxCmplt 0 RxOF 0 MalfTLP 0 ECRC 0 UnsupReq 0 ACSViol 0 UncorrIntErr 0 BlockedTLP 0 AtomicOpBlocked 0 TLPBlockedErr 0 PoisonTLPBlocked 0 TOTAL_ERR_FATAL 3` aerNonFatalData = ` Undefined 0 DLP 0 SDES 0 TLP 0 FCP 0 CmpltTO 2 CmpltAbrt 0 UnxCmplt 0 RxOF 0 MalfTLP 0 ECRC 0 UnsupReq 0 ACSViol 0 UncorrIntErr 0 BlockedTLP 0 AtomicOpBlocked 0 TLPBlockedErr 0 PoisonTLPBlocked 0 TOTAL_ERR_NONFATAL 9` ) var ( expectedRasMetrics = []telegraf.Metric{ testutil.MustMetric( "intel_dlb_ras", map[string]string{ "device": "0000:00:00.0", "metric_file": aerCorrectableFileName, }, map[string]interface{}{ "RxErr": uint64(1), "BadTLP": uint64(0), "BadDLLP": uint64(0), "Rollover": uint64(1), "Timeout": uint64(0), "NonFatalErr": uint64(0), "CorrIntErr": uint64(0), "HeaderOF": uint64(0), "TOTAL_ERR_COR": uint64(0), }, time.Unix(0, 0), ), testutil.MustMetric( "intel_dlb_ras", map[string]string{ "device": "0000:00:00.0", "metric_file": aerFatalFileName, }, map[string]interface{}{ "Undefined": uint64(0), "DLP": uint64(1), "SDES": uint64(0), "TLP": uint64(0), "FCP": uint64(0), "CmpltTO": uint64(0), "CmpltAbrt": uint64(0), "UnxCmplt": uint64(0), "RxOF": uint64(0), "MalfTLP": uint64(0), "ECRC": uint64(0), "UnsupReq": uint64(0), "ACSViol": uint64(0), "UncorrIntErr": uint64(0), "BlockedTLP": uint64(0), "AtomicOpBlocked": uint64(0), "TLPBlockedErr": uint64(0), "PoisonTLPBlocked": uint64(0), "TOTAL_ERR_FATAL": uint64(3), }, time.Unix(0, 0), ), testutil.MustMetric( "intel_dlb_ras", map[string]string{ "device": "0000:00:00.0", "metric_file": aerNonFatalFileName, }, map[string]interface{}{ "Undefined": uint64(0), "DLP": uint64(0), "SDES": uint64(0), "TLP": uint64(0), "FCP": uint64(0), "CmpltTO": uint64(2), "CmpltAbrt": uint64(0), "UnxCmplt": uint64(0), "RxOF": uint64(0), "MalfTLP": uint64(0), "ECRC": uint64(0), "UnsupReq": uint64(0), "ACSViol": uint64(0), "UncorrIntErr": uint64(0), "BlockedTLP": uint64(0), "AtomicOpBlocked": uint64(0), "TLPBlockedErr": uint64(0), "PoisonTLPBlocked": uint64(0), "TOTAL_ERR_NONFATAL": uint64(9), }, time.Unix(0, 0), ), } expectedTelegrafMetrics = []telegraf.Metric{ testutil.MustMetric( "intel_dlb", map[string]string{ "command": "/eventdev/dev_xstats,0", }, map[string]interface{}{ "dev_rx_ok": int64(0), }, time.Unix(0, 0), ), expectedRasMetrics[0], expectedRasMetrics[1], expectedRasMetrics[2], } )