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 }