github.com/kjdelisle/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  }