github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/event_endpoint.go (about)

     1  package nomad
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"io/ioutil"
     7  	"time"
     8  
     9  	"github.com/hashicorp/go-msgpack/codec"
    10  
    11  	"github.com/hashicorp/nomad/helper/pointer"
    12  	"github.com/hashicorp/nomad/nomad/stream"
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  )
    15  
    16  type Event struct {
    17  	srv *Server
    18  }
    19  
    20  func NewEventEndpoint(srv *Server) *Event {
    21  	return &Event{srv: srv}
    22  }
    23  
    24  func (e *Event) register() {
    25  	e.srv.streamingRpcs.Register("Event.Stream", e.stream)
    26  }
    27  
    28  func (e *Event) stream(conn io.ReadWriteCloser) {
    29  	defer conn.Close()
    30  
    31  	var args structs.EventStreamRequest
    32  	decoder := codec.NewDecoder(conn, structs.MsgpackHandle)
    33  	encoder := codec.NewEncoder(conn, structs.MsgpackHandle)
    34  
    35  	if err := decoder.Decode(&args); err != nil {
    36  		handleJsonResultError(err, pointer.Of(int64(500)), encoder)
    37  		return
    38  	}
    39  
    40  	// forward to appropriate region
    41  	if args.Region != e.srv.config.Region {
    42  		err := e.forwardStreamingRPC(args.Region, "Event.Stream", args, conn)
    43  		if err != nil {
    44  			handleJsonResultError(err, pointer.Of(int64(500)), encoder)
    45  		}
    46  		return
    47  	}
    48  
    49  	// Generate the subscription request
    50  	subReq := &stream.SubscribeRequest{
    51  		Token:     args.AuthToken,
    52  		Topics:    args.Topics,
    53  		Index:     uint64(args.Index),
    54  		Namespace: args.Namespace,
    55  	}
    56  
    57  	// Get the servers broker and subscribe
    58  	publisher, err := e.srv.State().EventBroker()
    59  	if err != nil {
    60  		handleJsonResultError(err, pointer.Of(int64(500)), encoder)
    61  		return
    62  	}
    63  
    64  	// start subscription to publisher
    65  	var subscription *stream.Subscription
    66  	var subErr error
    67  
    68  	// Track whether the ACL token being used has an expiry time.
    69  	var expiryTime *time.Time
    70  
    71  	// Check required ACL permissions for requested Topics
    72  	if e.srv.config.ACLEnabled {
    73  		subscription, expiryTime, subErr = publisher.SubscribeWithACLCheck(subReq)
    74  	} else {
    75  		subscription, subErr = publisher.Subscribe(subReq)
    76  	}
    77  	if subErr != nil {
    78  		handleJsonResultError(subErr, pointer.Of(int64(500)), encoder)
    79  		return
    80  	}
    81  	defer subscription.Unsubscribe()
    82  
    83  	ctx, cancel := context.WithCancel(context.Background())
    84  	defer cancel()
    85  	// goroutine to detect remote side closing
    86  	go func() {
    87  		io.Copy(ioutil.Discard, conn)
    88  		cancel()
    89  	}()
    90  
    91  	jsonStream := stream.NewJsonStream(ctx, 30*time.Second)
    92  	errCh := make(chan error)
    93  	go func() {
    94  		defer cancel()
    95  		for {
    96  			events, err := subscription.Next(ctx)
    97  			if err != nil {
    98  				select {
    99  				case errCh <- err:
   100  				case <-ctx.Done():
   101  				}
   102  				return
   103  			}
   104  
   105  			// Ensure the token being used is not expired before we any events
   106  			// to subscribers.
   107  			if expiryTime != nil && expiryTime.Before(time.Now().UTC()) {
   108  				select {
   109  				case errCh <- structs.ErrTokenExpired:
   110  				case <-ctx.Done():
   111  				}
   112  				return
   113  			}
   114  
   115  			// Continue if there are no events
   116  			if len(events.Events) == 0 {
   117  				continue
   118  			}
   119  
   120  			if err := jsonStream.Send(events); err != nil {
   121  				select {
   122  				case errCh <- err:
   123  				case <-ctx.Done():
   124  				}
   125  				return
   126  			}
   127  		}
   128  	}()
   129  
   130  	var streamErr error
   131  OUTER:
   132  	for {
   133  		select {
   134  		case streamErr = <-errCh:
   135  			break OUTER
   136  		case <-ctx.Done():
   137  			break OUTER
   138  		case eventJSON, ok := <-jsonStream.OutCh():
   139  			// check if ndjson may have been closed when an error occurred,
   140  			// check once more for an error.
   141  			if !ok {
   142  				select {
   143  				case streamErr = <-errCh:
   144  					// There was a pending error
   145  				default:
   146  				}
   147  				break OUTER
   148  			}
   149  
   150  			var resp structs.EventStreamWrapper
   151  			resp.Event = eventJSON
   152  
   153  			if err := encoder.Encode(resp); err != nil {
   154  				streamErr = err
   155  				break OUTER
   156  			}
   157  			encoder.Reset(conn)
   158  		}
   159  
   160  	}
   161  
   162  	if streamErr != nil {
   163  		handleJsonResultError(streamErr, pointer.Of(int64(500)), encoder)
   164  		return
   165  	}
   166  
   167  }
   168  
   169  func (e *Event) forwardStreamingRPC(region string, method string, args interface{}, in io.ReadWriteCloser) error {
   170  	server, err := e.srv.findRegionServer(region)
   171  	if err != nil {
   172  		return err
   173  	}
   174  
   175  	return e.forwardStreamingRPCToServer(server, method, args, in)
   176  }
   177  
   178  func (e *Event) forwardStreamingRPCToServer(server *serverParts, method string, args interface{}, in io.ReadWriteCloser) error {
   179  	srvConn, err := e.srv.streamingRpc(server, method)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	defer srvConn.Close()
   184  
   185  	outEncoder := codec.NewEncoder(srvConn, structs.MsgpackHandle)
   186  	if err := outEncoder.Encode(args); err != nil {
   187  		return err
   188  	}
   189  
   190  	structs.Bridge(in, srvConn)
   191  	return nil
   192  }
   193  
   194  // handleJsonResultError is a helper for sending an error with a potential
   195  // error code. The transmission of the error is ignored if the error has been
   196  // generated by the closing of the underlying transport.
   197  func handleJsonResultError(err error, code *int64, encoder *codec.Encoder) {
   198  	// Nothing to do as the conn is closed
   199  	if err == io.EOF {
   200  		return
   201  	}
   202  
   203  	encoder.Encode(&structs.EventStreamWrapper{
   204  		Error: structs.NewRpcError(err, code),
   205  	})
   206  }