github.com/Iqoqo/consul@v1.4.5/agent/event_endpoint.go (about) 1 package agent 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net/http" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/hashicorp/consul/acl" 13 "github.com/hashicorp/consul/agent/structs" 14 ) 15 16 const ( 17 // maxQueryTime is used to bound the limit of a blocking query 18 maxQueryTime = 600 * time.Second 19 ) 20 21 // EventFire is used to fire a new event 22 func (s *HTTPServer) EventFire(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 23 24 // Get the datacenter 25 var dc string 26 s.parseDC(req, &dc) 27 28 event := &UserEvent{} 29 event.Name = strings.TrimPrefix(req.URL.Path, "/v1/event/fire/") 30 if event.Name == "" { 31 resp.WriteHeader(http.StatusBadRequest) 32 fmt.Fprint(resp, "Missing name") 33 return nil, nil 34 } 35 36 // Get the ACL token 37 var token string 38 s.parseToken(req, &token) 39 40 // Get the filters 41 if filt := req.URL.Query().Get("node"); filt != "" { 42 event.NodeFilter = filt 43 } 44 if filt := req.URL.Query().Get("service"); filt != "" { 45 event.ServiceFilter = filt 46 } 47 if filt := req.URL.Query().Get("tag"); filt != "" { 48 event.TagFilter = filt 49 } 50 51 // Get the payload 52 if req.ContentLength > 0 { 53 var buf bytes.Buffer 54 if _, err := io.Copy(&buf, req.Body); err != nil { 55 return nil, err 56 } 57 event.Payload = buf.Bytes() 58 } 59 60 // Try to fire the event 61 if err := s.agent.UserEvent(dc, token, event); err != nil { 62 if acl.IsErrPermissionDenied(err) { 63 resp.WriteHeader(http.StatusForbidden) 64 fmt.Fprint(resp, acl.ErrPermissionDenied.Error()) 65 return nil, nil 66 } 67 resp.WriteHeader(http.StatusInternalServerError) 68 return nil, err 69 } 70 71 // Return the event 72 return event, nil 73 } 74 75 // EventList is used to retrieve the recent list of events 76 func (s *HTTPServer) EventList(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 77 // Parse the query options, since we simulate a blocking query 78 var b structs.QueryOptions 79 if parseWait(resp, req, &b) { 80 return nil, nil 81 } 82 83 // Fetch the ACL token, if any. 84 var token string 85 s.parseToken(req, &token) 86 acl, err := s.agent.resolveToken(token) 87 if err != nil { 88 return nil, err 89 } 90 91 // Look for a name filter 92 var nameFilter string 93 if filt := req.URL.Query().Get("name"); filt != "" { 94 nameFilter = filt 95 } 96 97 // Lots of this logic is borrowed from consul/rpc.go:blockingQuery 98 // However we cannot use that directly since this code has some 99 // slight semantics differences... 100 var timeout <-chan time.Time 101 var notifyCh chan struct{} 102 103 // Fast path non-blocking 104 if b.MinQueryIndex == 0 { 105 goto RUN_QUERY 106 } 107 108 // Restrict the max query time 109 if b.MaxQueryTime > maxQueryTime { 110 b.MaxQueryTime = maxQueryTime 111 } 112 113 // Ensure a time limit is set if we have an index 114 if b.MinQueryIndex > 0 && b.MaxQueryTime == 0 { 115 b.MaxQueryTime = maxQueryTime 116 } 117 118 // Setup a query timeout 119 if b.MaxQueryTime > 0 { 120 timeout = time.After(b.MaxQueryTime) 121 } 122 123 // Setup a notification channel for changes 124 SETUP_NOTIFY: 125 if b.MinQueryIndex > 0 { 126 notifyCh = make(chan struct{}, 1) 127 s.agent.eventNotify.Wait(notifyCh) 128 defer s.agent.eventNotify.Clear(notifyCh) 129 } 130 131 RUN_QUERY: 132 // Get the recent events 133 events := s.agent.UserEvents() 134 135 // Filter the events using the ACL, if present 136 if acl != nil { 137 for i := 0; i < len(events); i++ { 138 name := events[i].Name 139 if acl.EventRead(name) { 140 continue 141 } 142 s.agent.logger.Printf("[DEBUG] agent: dropping event %q from result due to ACLs", name) 143 events = append(events[:i], events[i+1:]...) 144 i-- 145 } 146 } 147 148 // Filter the events if requested 149 if nameFilter != "" { 150 for i := 0; i < len(events); i++ { 151 if events[i].Name != nameFilter { 152 events = append(events[:i], events[i+1:]...) 153 i-- 154 } 155 } 156 } 157 158 // Determine the index 159 var index uint64 160 if len(events) == 0 { 161 // Return a non-zero index to prevent a hot query loop. This 162 // can be caused by a watch for example when there is no matching 163 // events. 164 index = 1 165 } else { 166 last := events[len(events)-1] 167 index = uuidToUint64(last.ID) 168 } 169 setIndex(resp, index) 170 171 // Check for exact match on the query value. Because 172 // the index value is not monotonic, we just ensure it is 173 // not an exact match. 174 if index > 0 && index == b.MinQueryIndex { 175 select { 176 case <-notifyCh: 177 goto SETUP_NOTIFY 178 case <-timeout: 179 } 180 } 181 return events, nil 182 } 183 184 // uuidToUint64 is a bit of a hack to generate a 64bit Consul index. 185 // In effect, we take our random UUID, convert it to a 128 bit number, 186 // then XOR the high-order and low-order 64bit's together to get the 187 // output. This lets us generate an index which can be used to simulate 188 // the blocking behavior of other catalog endpoints. 189 func uuidToUint64(uuid string) uint64 { 190 lower := uuid[0:8] + uuid[9:13] + uuid[14:18] 191 upper := uuid[19:23] + uuid[24:36] 192 lowVal, err := strconv.ParseUint(lower, 16, 64) 193 if err != nil { 194 panic("Failed to convert " + lower) 195 } 196 highVal, err := strconv.ParseUint(upper, 16, 64) 197 if err != nil { 198 panic("Failed to convert " + upper) 199 } 200 return lowVal ^ highVal 201 }