github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/daemon/logger/splunk/splunkhecmock_test.go (about)

     1  package splunk // import "github.com/docker/docker/daemon/logger/splunk"
     2  
     3  import (
     4  	"compress/gzip"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net"
    11  	"net/http"
    12  	"sync"
    13  	"testing"
    14  )
    15  
    16  func (message *splunkMessage) EventAsString() (string, error) {
    17  	if val, ok := message.Event.(string); ok {
    18  		return val, nil
    19  	}
    20  	return "", fmt.Errorf("Cannot cast Event %v to string", message.Event)
    21  }
    22  
    23  func (message *splunkMessage) EventAsMap() (map[string]interface{}, error) {
    24  	if val, ok := message.Event.(map[string]interface{}); ok {
    25  		return val, nil
    26  	}
    27  	return nil, fmt.Errorf("Cannot cast Event %v to map", message.Event)
    28  }
    29  
    30  type HTTPEventCollectorMock struct {
    31  	tcpAddr     *net.TCPAddr
    32  	tcpListener *net.TCPListener
    33  
    34  	mu                  sync.Mutex
    35  	token               string
    36  	simulateServerError bool
    37  	blockingCtx         context.Context
    38  
    39  	test *testing.T
    40  
    41  	connectionVerified bool
    42  	gzipEnabled        *bool
    43  	messages           []*splunkMessage
    44  	numOfRequests      int
    45  }
    46  
    47  func NewHTTPEventCollectorMock(t *testing.T) *HTTPEventCollectorMock {
    48  	tcpAddr := &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 0, Zone: ""}
    49  	tcpListener, err := net.ListenTCP("tcp", tcpAddr)
    50  	if err != nil {
    51  		t.Fatal(err)
    52  	}
    53  	return &HTTPEventCollectorMock{
    54  		tcpAddr:             tcpAddr,
    55  		tcpListener:         tcpListener,
    56  		token:               "4642492F-D8BD-47F1-A005-0C08AE4657DF",
    57  		simulateServerError: false,
    58  		test:                t,
    59  		connectionVerified:  false}
    60  }
    61  
    62  func (hec *HTTPEventCollectorMock) simulateErr(b bool) {
    63  	hec.mu.Lock()
    64  	hec.simulateServerError = b
    65  	hec.mu.Unlock()
    66  }
    67  
    68  func (hec *HTTPEventCollectorMock) withBlock(ctx context.Context) {
    69  	hec.mu.Lock()
    70  	hec.blockingCtx = ctx
    71  	hec.mu.Unlock()
    72  }
    73  
    74  func (hec *HTTPEventCollectorMock) URL() string {
    75  	return "http://" + hec.tcpListener.Addr().String()
    76  }
    77  
    78  func (hec *HTTPEventCollectorMock) Serve() error {
    79  	return http.Serve(hec.tcpListener, hec)
    80  }
    81  
    82  func (hec *HTTPEventCollectorMock) Close() error {
    83  	return hec.tcpListener.Close()
    84  }
    85  
    86  func (hec *HTTPEventCollectorMock) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
    87  	var err error
    88  
    89  	hec.numOfRequests++
    90  
    91  	hec.mu.Lock()
    92  	simErr := hec.simulateServerError
    93  	ctx := hec.blockingCtx
    94  	hec.mu.Unlock()
    95  
    96  	if ctx != nil {
    97  		<-hec.blockingCtx.Done()
    98  	}
    99  
   100  	if simErr {
   101  		if request.Body != nil {
   102  			defer request.Body.Close()
   103  		}
   104  		writer.WriteHeader(http.StatusInternalServerError)
   105  		return
   106  	}
   107  
   108  	switch request.Method {
   109  	case http.MethodOptions:
   110  		// Verify that options method is getting called only once
   111  		if hec.connectionVerified {
   112  			hec.test.Errorf("Connection should not be verified more than once. Got second request with %s method.", request.Method)
   113  		}
   114  		hec.connectionVerified = true
   115  		writer.WriteHeader(http.StatusOK)
   116  	case http.MethodPost:
   117  		// Always verify that Driver is using correct path to HEC
   118  		if request.URL.String() != "/services/collector/event/1.0" {
   119  			hec.test.Errorf("Unexpected path %v", request.URL)
   120  		}
   121  		defer request.Body.Close()
   122  
   123  		if authorization, ok := request.Header["Authorization"]; !ok || authorization[0] != ("Splunk "+hec.token) {
   124  			hec.test.Error("Authorization header is invalid.")
   125  		}
   126  
   127  		gzipEnabled := false
   128  		if contentEncoding, ok := request.Header["Content-Encoding"]; ok && contentEncoding[0] == "gzip" {
   129  			gzipEnabled = true
   130  		}
   131  
   132  		if hec.gzipEnabled == nil {
   133  			hec.gzipEnabled = &gzipEnabled
   134  		} else if gzipEnabled != *hec.gzipEnabled {
   135  			// Nothing wrong with that, but we just know that Splunk Logging Driver does not do that
   136  			hec.test.Error("Driver should not change Content Encoding.")
   137  		}
   138  
   139  		var gzipReader *gzip.Reader
   140  		var reader io.Reader
   141  		if gzipEnabled {
   142  			gzipReader, err = gzip.NewReader(request.Body)
   143  			if err != nil {
   144  				hec.test.Fatal(err)
   145  			}
   146  			reader = gzipReader
   147  		} else {
   148  			reader = request.Body
   149  		}
   150  
   151  		// Read body
   152  		var body []byte
   153  		body, err = ioutil.ReadAll(reader)
   154  		if err != nil {
   155  			hec.test.Fatal(err)
   156  		}
   157  
   158  		// Parse message
   159  		messageStart := 0
   160  		for i := 0; i < len(body); i++ {
   161  			if i == len(body)-1 || (body[i] == '}' && body[i+1] == '{') {
   162  				var message splunkMessage
   163  				err = json.Unmarshal(body[messageStart:i+1], &message)
   164  				if err != nil {
   165  					hec.test.Log(string(body[messageStart : i+1]))
   166  					hec.test.Fatal(err)
   167  				}
   168  				hec.messages = append(hec.messages, &message)
   169  				messageStart = i + 1
   170  			}
   171  		}
   172  
   173  		if gzipEnabled {
   174  			gzipReader.Close()
   175  		}
   176  
   177  		writer.WriteHeader(http.StatusOK)
   178  	default:
   179  		hec.test.Errorf("Unexpected HTTP method %s", http.MethodOptions)
   180  		writer.WriteHeader(http.StatusBadRequest)
   181  	}
   182  }