github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/plugins/device/client.go (about)

     1  package device
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"time"
     7  
     8  	"github.com/LK4D4/joincontext"
     9  	"github.com/golang/protobuf/ptypes"
    10  	"github.com/hashicorp/nomad/helper/pluginutils/grpcutils"
    11  	"github.com/hashicorp/nomad/plugins/base"
    12  	"github.com/hashicorp/nomad/plugins/device/proto"
    13  )
    14  
    15  // devicePluginClient implements the client side of a remote device plugin, using
    16  // gRPC to communicate to the remote plugin.
    17  type devicePluginClient struct {
    18  	// basePluginClient is embedded to give access to the base plugin methods.
    19  	*base.BasePluginClient
    20  
    21  	client proto.DevicePluginClient
    22  
    23  	// doneCtx is closed when the plugin exits
    24  	doneCtx context.Context
    25  }
    26  
    27  // Fingerprint is used to retrieve the set of devices and their health from the
    28  // device plugin. An error may be immediately returned if the fingerprint call
    29  // could not be made or as part of the streaming response. If the context is
    30  // cancelled, the error will be propagated.
    31  func (d *devicePluginClient) Fingerprint(ctx context.Context) (<-chan *FingerprintResponse, error) {
    32  	// Join the passed context and the shutdown context
    33  	joinedCtx, _ := joincontext.Join(ctx, d.doneCtx)
    34  
    35  	var req proto.FingerprintRequest
    36  	stream, err := d.client.Fingerprint(joinedCtx, &req)
    37  	if err != nil {
    38  		return nil, grpcutils.HandleReqCtxGrpcErr(err, ctx, d.doneCtx)
    39  	}
    40  
    41  	out := make(chan *FingerprintResponse, 1)
    42  	go d.handleFingerprint(ctx, stream, out)
    43  	return out, nil
    44  }
    45  
    46  // handleFingerprint should be launched in a goroutine and handles converting
    47  // the gRPC stream to a channel. Exits either when context is cancelled or the
    48  // stream has an error.
    49  func (d *devicePluginClient) handleFingerprint(
    50  	reqCtx context.Context,
    51  	stream proto.DevicePlugin_FingerprintClient,
    52  	out chan *FingerprintResponse) {
    53  
    54  	defer close(out)
    55  	for {
    56  		resp, err := stream.Recv()
    57  		if err != nil {
    58  			if err != io.EOF {
    59  				out <- &FingerprintResponse{
    60  					Error: grpcutils.HandleReqCtxGrpcErr(err, reqCtx, d.doneCtx),
    61  				}
    62  			}
    63  
    64  			// End the stream
    65  			return
    66  		}
    67  
    68  		// Send the response
    69  		f := &FingerprintResponse{
    70  			Devices: convertProtoDeviceGroups(resp.GetDeviceGroup()),
    71  		}
    72  		select {
    73  		case <-reqCtx.Done():
    74  			return
    75  		case out <- f:
    76  		}
    77  	}
    78  }
    79  
    80  func (d *devicePluginClient) Reserve(deviceIDs []string) (*ContainerReservation, error) {
    81  	// Build the request
    82  	req := &proto.ReserveRequest{
    83  		DeviceIds: deviceIDs,
    84  	}
    85  
    86  	// Make the request
    87  	resp, err := d.client.Reserve(d.doneCtx, req)
    88  	if err != nil {
    89  		return nil, grpcutils.HandleGrpcErr(err, d.doneCtx)
    90  	}
    91  
    92  	// Convert the response
    93  	out := convertProtoContainerReservation(resp.GetContainerRes())
    94  	return out, nil
    95  }
    96  
    97  // Stats is used to retrieve device statistics from the device plugin. An error
    98  // may be immediately returned if the stats call could not be made or as part of
    99  // the streaming response. If the context is cancelled, the error will be
   100  // propagated.
   101  func (d *devicePluginClient) Stats(ctx context.Context, interval time.Duration) (<-chan *StatsResponse, error) {
   102  	// Join the passed context and the shutdown context
   103  	joinedCtx, _ := joincontext.Join(ctx, d.doneCtx)
   104  
   105  	req := proto.StatsRequest{
   106  		CollectionInterval: ptypes.DurationProto(interval),
   107  	}
   108  	stream, err := d.client.Stats(joinedCtx, &req)
   109  	if err != nil {
   110  		return nil, grpcutils.HandleReqCtxGrpcErr(err, ctx, d.doneCtx)
   111  	}
   112  
   113  	out := make(chan *StatsResponse, 1)
   114  	go d.handleStats(ctx, stream, out)
   115  	return out, nil
   116  }
   117  
   118  // handleStats should be launched in a goroutine and handles converting
   119  // the gRPC stream to a channel. Exits either when context is cancelled or the
   120  // stream has an error.
   121  func (d *devicePluginClient) handleStats(
   122  	reqCtx context.Context,
   123  	stream proto.DevicePlugin_StatsClient,
   124  	out chan *StatsResponse) {
   125  
   126  	defer close(out)
   127  	for {
   128  		resp, err := stream.Recv()
   129  		if err != nil {
   130  			if err != io.EOF {
   131  				out <- &StatsResponse{
   132  					Error: grpcutils.HandleReqCtxGrpcErr(err, reqCtx, d.doneCtx),
   133  				}
   134  			}
   135  
   136  			// End the stream
   137  			return
   138  		}
   139  
   140  		// Send the response
   141  		s := &StatsResponse{
   142  			Groups: convertProtoDeviceGroupsStats(resp.GetGroups()),
   143  		}
   144  		select {
   145  		case <-reqCtx.Done():
   146  			return
   147  		case out <- s:
   148  		}
   149  	}
   150  }