diff --git a/internal/provider/client/client.go b/internal/provider/client/client.go index 1791a938..45bc4cac 100644 --- a/internal/provider/client/client.go +++ b/internal/provider/client/client.go @@ -33,11 +33,12 @@ import ( ) const ( - DefaultMaxRetries int = 2 - DefaultBackoffMinDelay int = 4 - DefaultBackoffMaxDelay int = 60 - DefaultBackoffDelayFactor float64 = 3 - GnmiTimeout = 15 * time.Second + DefaultMaxRetries int = 2 + DefaultBackoffMinDelay int = 4 + DefaultBackoffMaxDelay int = 60 + DefaultBackoffDelayFactor float64 = 3 + DefaultGnmiTimeout time.Duration = 15 * time.Second // Configurable gNMI timeout + DefaultConnectTimeout time.Duration = 30 * time.Second // Configurable connection timeout ) type SetOperationType string @@ -60,6 +61,10 @@ type Client struct { BackoffMaxDelay int // Backoff delay factor BackoffDelayFactor float64 + // Connection timeout - configurable by user + ConnectTimeout time.Duration + // gNMI operation timeout - configurable by user + GnmiTimeout time.Duration } type Device struct { @@ -73,16 +78,21 @@ type SetOperation struct { Operation SetOperationType } -func NewClient(reuseConnection bool) Client { +func NewClient(reuseConnection bool, options ...interface{}) Client { devices := make(map[string]*Device) - return Client{ + + // Set default values + client := Client{ Devices: devices, ReuseConnection: reuseConnection, MaxRetries: DefaultMaxRetries, BackoffMinDelay: DefaultBackoffMinDelay, BackoffMaxDelay: DefaultBackoffMaxDelay, BackoffDelayFactor: DefaultBackoffDelayFactor, + ConnectTimeout: DefaultConnectTimeout, // Default 30 seconds + GnmiTimeout: DefaultGnmiTimeout, // Default 15 seconds } + return client } func (c *Client) AddTarget(ctx context.Context, device, host, username, password, certificate, key, caCertificate string, verifyCertificate, Tls bool) error { @@ -106,7 +116,11 @@ func (c *Client) AddTarget(ctx context.Context, device, host, username, password } if c.ReuseConnection { - err = t.CreateGNMIClient(ctx) + // Create connection context with configurable timeout + connectCtx, cancel := context.WithTimeout(ctx, c.ConnectTimeout) + defer cancel() + tflog.Debug(ctx, fmt.Sprintf("Creating gNMI client with connection timeout: %v", c.ConnectTimeout)) + err = t.CreateGNMIClient(connectCtx) if err != nil { return fmt.Errorf("Unable to create gNMI client: %w", err) } @@ -146,7 +160,12 @@ func (c *Client) Set(ctx context.Context, device string, operations ...SetOperat for attempts := 0; ; attempts++ { c.Devices[device].SetMutex.Lock() if !c.ReuseConnection { - err = target.CreateGNMIClient(ctx) + // Create connection context with configurable timeout + connectCtx, cancel := context.WithTimeout(ctx, c.ConnectTimeout) + tflog.Debug(ctx, fmt.Sprintf("Creating gNMI client with connection timeout: %v", c.ConnectTimeout)) + err = target.CreateGNMIClient(connectCtx) + cancel() // Clean up timeout context immediately + if err != nil { if ok := c.Backoff(ctx, attempts); !ok { return nil, fmt.Errorf("Unable to create gNMI client: %w", err) @@ -156,9 +175,10 @@ func (c *Client) Set(ctx context.Context, device string, operations ...SetOperat } } } - tCtx, cancel := context.WithTimeout(ctx, GnmiTimeout) + // Use configurable gNMI timeout + tCtx, cancel := context.WithTimeout(ctx, c.GnmiTimeout) defer cancel() - tflog.Debug(ctx, fmt.Sprintf("gNMI set request: %s", setReq.String())) + tflog.Debug(ctx, fmt.Sprintf("gNMI set request (timeout: %v): %s", c.GnmiTimeout, setReq.String())) setResp, err = target.Set(tCtx, setReq) tflog.Debug(ctx, fmt.Sprintf("gNMI set response: %s", setResp.String())) c.Devices[device].SetMutex.Unlock() @@ -195,7 +215,12 @@ func (c *Client) Get(ctx context.Context, device, path string) (*gnmi.GetRespons for attempts := 0; ; attempts++ { tflog.Debug(ctx, fmt.Sprintf("gNMI get request: %s", getReq.String())) if !c.ReuseConnection { - err = target.CreateGNMIClient(ctx) + // Create connection context with configurable timeout + connectCtx, cancel := context.WithTimeout(ctx, c.ConnectTimeout) + tflog.Debug(ctx, fmt.Sprintf("Creating gNMI client with connection timeout: %v", c.ConnectTimeout)) + err = target.CreateGNMIClient(connectCtx) + cancel() // Clean up timeout context immediately + if err != nil { if ok := c.Backoff(ctx, attempts); !ok { return nil, fmt.Errorf("Unable to create gNMI client: %w", err) @@ -205,8 +230,10 @@ func (c *Client) Get(ctx context.Context, device, path string) (*gnmi.GetRespons } } } - tCtx, cancel := context.WithTimeout(ctx, GnmiTimeout) + // Use configurable gNMI timeout + tCtx, cancel := context.WithTimeout(ctx, c.GnmiTimeout) defer cancel() + tflog.Debug(ctx, fmt.Sprintf("gNMI get request (timeout: %v)", c.GnmiTimeout)) getResp, err = target.Get(tCtx, getReq) if !c.ReuseConnection { target.Close() @@ -214,7 +241,7 @@ func (c *Client) Get(ctx context.Context, device, path string) (*gnmi.GetRespons tflog.Debug(ctx, fmt.Sprintf("gNMI get response: %s", getResp.String())) if err != nil { if ok := c.Backoff(ctx, attempts); !ok { - return nil, fmt.Errorf(("Get request failed, got error: %w"), err) + return nil, fmt.Errorf("Get request failed, got error: %w", err) } else { tflog.Error(ctx, fmt.Sprintf("gNMI get request failed: %s, retries: %v", err, attempts)) continue