go.ligato.io/vpp-agent/v3@v3.5.0/plugins/govppmux/client_binapi.go (about)

     1  //  Copyright (c) 2019 Cisco and/or its affiliates.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at:
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package govppmux
    16  
    17  import (
    18  	"context"
    19  	"runtime/trace"
    20  	"time"
    21  
    22  	govppapi "go.fd.io/govpp/api"
    23  	"go.fd.io/govpp/core"
    24  	"go.ligato.io/cn-infra/v2/logging"
    25  )
    26  
    27  func (p *Plugin) NewStream(ctx context.Context, options ...govppapi.StreamOption) (govppapi.Stream, error) {
    28  	return p.vppConn.NewStream(ctx, options...)
    29  }
    30  
    31  func (p *Plugin) Invoke(ctx context.Context, req govppapi.Message, reply govppapi.Message) error {
    32  	return p.vppConn.Invoke(ctx, req, reply)
    33  }
    34  
    35  func (p *Plugin) WatchEvent(ctx context.Context, event govppapi.Message) (govppapi.Watcher, error) {
    36  	return p.vppConn.WatchEvent(ctx, event)
    37  }
    38  
    39  // NewAPIChannel returns a new API channel for communication with VPP via govpp core.
    40  // It uses default buffer sizes for the request and reply Go channels.
    41  func (p *Plugin) NewAPIChannel() (govppapi.Channel, error) {
    42  	ch, err := p.vppConn.NewAPIChannel()
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	retryCfg := retryConfig{
    47  		p.config.RetryRequestCount,
    48  		p.config.RetryRequestTimeout,
    49  	}
    50  	return newGovppChan(ch, retryCfg), nil
    51  }
    52  
    53  // NewAPIChannelBuffered returns a new API channel for communication with VPP via govpp core.
    54  // It allows to specify custom buffer sizes for the request and reply Go channels.
    55  func (p *Plugin) NewAPIChannelBuffered(reqChanBufSize, replyChanBufSize int) (govppapi.Channel, error) {
    56  	ch, err := p.vppConn.NewAPIChannelBuffered(reqChanBufSize, replyChanBufSize)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	retryCfg := retryConfig{
    61  		p.config.RetryRequestCount,
    62  		p.config.RetryRequestTimeout,
    63  	}
    64  	return newGovppChan(ch, retryCfg), nil
    65  }
    66  
    67  // goVppChan implements govpp channel interface. Instance is returned by NewAPIChannel() or NewAPIChannelBuffered(),
    68  // and contains *govpp.channel dynamic type (vppChan field). Implemented methods allow custom handling of low-level
    69  // govpp.
    70  type goVppChan struct {
    71  	govppapi.Channel
    72  	// Retry data
    73  	retry retryConfig
    74  }
    75  
    76  func newGovppChan(ch govppapi.Channel, retryCfg retryConfig) *goVppChan {
    77  	govppChan := &goVppChan{
    78  		Channel: ch,
    79  		retry:   retryCfg,
    80  	}
    81  	reportChannelsOpened()
    82  	return govppChan
    83  }
    84  
    85  func (c *goVppChan) Close() {
    86  	c.Channel.Close()
    87  	reportChannelsClosed()
    88  }
    89  
    90  // helper struct holding info about retry configuration
    91  type retryConfig struct {
    92  	attempts int
    93  	timeout  time.Duration
    94  }
    95  
    96  // govppRequestCtx is custom govpp RequestCtx.
    97  type govppRequestCtx struct {
    98  	ctx  context.Context
    99  	task *trace.Task
   100  
   101  	// Original request context
   102  	requestCtx govppapi.RequestCtx
   103  	// Function allowing to re-send request in case it's granted by the config file
   104  	sendRequest func(govppapi.Message) govppapi.RequestCtx
   105  	// Parameter for sendRequest
   106  	requestMsg govppapi.Message
   107  
   108  	retry retryConfig
   109  	start time.Time
   110  }
   111  
   112  // govppMultirequestCtx is custom govpp MultiRequestCtx.
   113  type govppMultirequestCtx struct {
   114  	ctx  context.Context
   115  	task *trace.Task
   116  
   117  	// Original multi request context
   118  	requestCtx govppapi.MultiRequestCtx
   119  	// Parameter for sendRequest
   120  	requestMsg govppapi.Message
   121  
   122  	start time.Time
   123  }
   124  
   125  // SendRequest sends asynchronous request to the vpp and receives context used to receive reply.
   126  // Plugin govppmux allows to re-send retry which failed because of disconnected vpp, if enabled.
   127  func (c *goVppChan) SendRequest(request govppapi.Message) govppapi.RequestCtx {
   128  	ctx, task := trace.NewTask(context.Background(), "govpp.SendRequest")
   129  	trace.Log(ctx, "messageName", request.GetMessageName())
   130  
   131  	start := time.Now()
   132  	// Send request now and wait for context
   133  	requestCtx := c.Channel.SendRequest(request)
   134  
   135  	reportRequestSent(request)
   136  
   137  	// Return context with value and function which allows to send request again if needed
   138  	return &govppRequestCtx{
   139  		ctx:         ctx,
   140  		task:        task,
   141  		requestCtx:  requestCtx,
   142  		sendRequest: c.Channel.SendRequest,
   143  		requestMsg:  request,
   144  		retry:       c.retry,
   145  		start:       start,
   146  	}
   147  }
   148  
   149  // ReceiveReply handles request and returns error if occurred. Also does retry if this option is available.
   150  func (r *govppRequestCtx) ReceiveReply(reply govppapi.Message) error {
   151  	defer r.task.End()
   152  
   153  	var timeout time.Duration
   154  	attempts := r.retry.attempts
   155  	if r.retry.timeout > 0 { // Default value is 500ms
   156  		timeout = r.retry.timeout
   157  	}
   158  	// Receive reply from original send
   159  	err := r.requestCtx.ReceiveReply(reply)
   160  	for retry := 1; err == core.ErrNotConnected; retry++ {
   161  		if retry > attempts {
   162  			break // max attempts exceeded
   163  		}
   164  		logging.Warnf("govppmux: request retry (%d/%d), message %s in %v",
   165  			retry, attempts, r.requestMsg.GetMessageName(), timeout)
   166  		// Wait before next attempt
   167  		time.Sleep(timeout)
   168  		// Retry request
   169  		trace.Logf(r.ctx, "requestRetry", "%d/%d", retry, attempts)
   170  		err = r.sendRequest(r.requestMsg).ReceiveReply(reply)
   171  	}
   172  	if err != nil {
   173  		reportRequestFailed(reply, err)
   174  	} else {
   175  		reportRequestSuccess(r.requestMsg, r.start)
   176  		reportRepliesReceived(reply)
   177  	}
   178  	return err
   179  }
   180  
   181  // SendMultiRequest sends asynchronous request to the vpp and receives context used to receive reply.
   182  func (c *goVppChan) SendMultiRequest(request govppapi.Message) govppapi.MultiRequestCtx {
   183  	ctx, task := trace.NewTask(context.Background(), "govpp.SendMultiRequest")
   184  	trace.Log(ctx, "msgName", request.GetMessageName())
   185  
   186  	start := time.Now()
   187  	// Send request now and wait for context
   188  	requestCtx := c.Channel.SendMultiRequest(request)
   189  
   190  	reportRequestSent(request)
   191  
   192  	// Return context with value and function which allows to send request again if needed
   193  	return &govppMultirequestCtx{
   194  		ctx:        ctx,
   195  		task:       task,
   196  		requestCtx: requestCtx,
   197  		requestMsg: request,
   198  		start:      start,
   199  	}
   200  }
   201  
   202  // ReceiveReply handles request and returns error if occurred.
   203  func (r *govppMultirequestCtx) ReceiveReply(reply govppapi.Message) (bool, error) {
   204  	// Receive reply from original send
   205  	last, err := r.requestCtx.ReceiveReply(reply)
   206  	if last || err != nil {
   207  		defer r.task.End()
   208  		if err != nil {
   209  			reportRequestFailed(r.requestMsg, err)
   210  		} else {
   211  			reportRequestSuccess(r.requestMsg, r.start)
   212  		}
   213  	} else {
   214  		reportRepliesReceived(reply)
   215  	}
   216  	return last, err
   217  }