github.com/hernad/nomad@v1.6.112/nomad/event_endpoint.go (about)

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