163 lines
4.5 KiB
Go
163 lines
4.5 KiB
Go
//go:build linux
|
|
|
|
package dpdk
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/influxdata/telegraf/config"
|
|
)
|
|
|
|
const maxInitMessageLength = 1024
|
|
|
|
type initMessage struct {
|
|
Version string `json:"version"`
|
|
Pid int `json:"pid"`
|
|
MaxOutputLen uint32 `json:"max_output_len"`
|
|
}
|
|
|
|
type dpdkConnector struct {
|
|
pathToSocket string
|
|
maxOutputLen uint32
|
|
messageShowed bool
|
|
accessTimeout time.Duration
|
|
connection net.Conn
|
|
}
|
|
|
|
func newDpdkConnector(pathToSocket string, accessTimeout config.Duration) *dpdkConnector {
|
|
return &dpdkConnector{
|
|
pathToSocket: pathToSocket,
|
|
messageShowed: false,
|
|
accessTimeout: time.Duration(accessTimeout),
|
|
}
|
|
}
|
|
|
|
// Connects to the socket
|
|
// Since DPDK is a local unix socket, it is instantly returns error or connection, so there's no need to set timeout for it
|
|
func (conn *dpdkConnector) connect() (*initMessage, error) {
|
|
connection, err := net.Dial("unixpacket", conn.pathToSocket)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to connect to the socket - %v", err)
|
|
}
|
|
|
|
conn.connection = connection
|
|
result, err := conn.readMaxOutputLen()
|
|
if err != nil {
|
|
if closeErr := conn.tryClose(); closeErr != nil {
|
|
return nil, fmt.Errorf("%v and failed to close connection - %v", err, closeErr)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Executes command using provided connection and returns response
|
|
// If error (such as timeout) occurred, then connection is discarded and recreated
|
|
// because otherwise behaviour of connection is undefined (e.g. it could return result of timed out command instead of latest)
|
|
func (conn *dpdkConnector) getCommandResponse(fullCommand string) ([]byte, error) {
|
|
connection, err := conn.getConnection()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get connection to execute %v command - %v", fullCommand, err)
|
|
}
|
|
|
|
err = conn.setTimeout()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to set timeout for %v command - %v", fullCommand, err)
|
|
}
|
|
|
|
_, err = connection.Write([]byte(fullCommand))
|
|
if err != nil {
|
|
if closeErr := conn.tryClose(); closeErr != nil {
|
|
return nil, fmt.Errorf("failed to send '%v' command - %v and failed to close connection - %v",
|
|
fullCommand, err, closeErr)
|
|
}
|
|
return nil, fmt.Errorf("failed to send '%v' command - %v", fullCommand, err)
|
|
}
|
|
|
|
buf := make([]byte, conn.maxOutputLen)
|
|
messageLength, err := connection.Read(buf)
|
|
if err != nil {
|
|
if closeErr := conn.tryClose(); closeErr != nil {
|
|
return nil, fmt.Errorf("failed read response of '%v' command - %v and failed to close connection - %v",
|
|
fullCommand, err, closeErr)
|
|
}
|
|
return nil, fmt.Errorf("failed to read response of '%v' command - %v", fullCommand, err)
|
|
}
|
|
|
|
if messageLength == 0 {
|
|
return nil, fmt.Errorf("got empty response during execution of '%v' command", fullCommand)
|
|
}
|
|
return buf[:messageLength], nil
|
|
}
|
|
|
|
func (conn *dpdkConnector) tryClose() error {
|
|
if conn.connection == nil {
|
|
return nil
|
|
}
|
|
|
|
err := conn.connection.Close()
|
|
conn.connection = nil
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (conn *dpdkConnector) setTimeout() error {
|
|
if conn.connection == nil {
|
|
return fmt.Errorf("connection had not been established before")
|
|
}
|
|
|
|
if conn.accessTimeout == 0 {
|
|
return conn.connection.SetDeadline(time.Time{})
|
|
}
|
|
return conn.connection.SetDeadline(time.Now().Add(conn.accessTimeout))
|
|
}
|
|
|
|
// Returns connections, if connection is not created then function tries to recreate it
|
|
func (conn *dpdkConnector) getConnection() (net.Conn, error) {
|
|
if conn.connection == nil {
|
|
_, err := conn.connect()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return conn.connection, nil
|
|
}
|
|
|
|
// Reads InitMessage for connection. Should be read for each connection, otherwise InitMessage is returned as response for first command.
|
|
func (conn *dpdkConnector) readMaxOutputLen() (*initMessage, error) {
|
|
buf := make([]byte, maxInitMessageLength)
|
|
err := conn.setTimeout()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to set timeout - %v", err)
|
|
}
|
|
|
|
messageLength, err := conn.connection.Read(buf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read InitMessage - %v", err)
|
|
}
|
|
|
|
var initMessage initMessage
|
|
err = json.Unmarshal(buf[:messageLength], &initMessage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal response - %v", err)
|
|
}
|
|
|
|
if initMessage.MaxOutputLen == 0 {
|
|
return nil, fmt.Errorf("failed to read maxOutputLen information")
|
|
}
|
|
|
|
if !conn.messageShowed {
|
|
conn.maxOutputLen = initMessage.MaxOutputLen
|
|
conn.messageShowed = true
|
|
return &initMessage, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|