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 }