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