github.com/google/cloudprober@v0.11.3/servers/http/http_test.go (about)

     1  // Copyright 2017 The Cloudprober Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package http
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"net"
    22  	"net/http"
    23  	"os"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/cloudprober/logger"
    29  	"github.com/google/cloudprober/metrics"
    30  	"github.com/google/cloudprober/targets/endpoint"
    31  )
    32  
    33  const testExportInterval = 2 * time.Second
    34  
    35  type fakeLameduckLister struct {
    36  	lameducked []string
    37  	err        error
    38  }
    39  
    40  func (f *fakeLameduckLister) ListEndpoints() []endpoint.Endpoint {
    41  	return endpoint.EndpointsFromNames(f.lameducked)
    42  }
    43  
    44  func TestMain(m *testing.M) {
    45  	os.Exit(m.Run())
    46  }
    47  
    48  func testServer(ctx context.Context, t *testing.T, insName string, ldLister endpoint.Lister) (*Server, chan *metrics.EventMetrics) {
    49  	ln, err := net.Listen("tcp", ":0")
    50  	if err != nil {
    51  		t.Fatalf("Listen error: %v.", err)
    52  	}
    53  
    54  	dataChan := make(chan *metrics.EventMetrics, 10)
    55  	s := &Server{
    56  		l:             &logger.Logger{},
    57  		ln:            ln,
    58  		statsInterval: 2 * time.Second,
    59  		instanceName:  insName,
    60  		ldLister:      ldLister,
    61  		reqMetric:     metrics.NewMap("url", metrics.NewInt(0)),
    62  		sysVars:       map[string]string{"instance": insName},
    63  		staticURLResTable: map[string][]byte{
    64  			"/":         []byte(OK),
    65  			"/instance": []byte(insName),
    66  		},
    67  	}
    68  
    69  	go func() {
    70  		s.Start(ctx, dataChan)
    71  	}()
    72  
    73  	return s, dataChan
    74  }
    75  
    76  // get preforms HTTP GET request and return the response body and status
    77  func get(t *testing.T, ln net.Listener, path string) (string, string) {
    78  	t.Helper()
    79  	resp, err := http.Get(fmt.Sprintf("http://%s/%s", listenerAddr(ln), path))
    80  	if err != nil {
    81  		t.Errorf("HTTP server returned an error for the URL '/%s'. Err: %v", path, err)
    82  		return "", ""
    83  	}
    84  	status := resp.Status
    85  	defer resp.Body.Close()
    86  	body, err := ioutil.ReadAll(resp.Body)
    87  	if err != nil {
    88  		t.Errorf("Error while reading response for the URL '/%s': Err: %v", path, err)
    89  		return "", status
    90  	}
    91  	return string(body), status
    92  }
    93  
    94  func listenerAddr(ln net.Listener) string {
    95  	return fmt.Sprintf("localhost:%d", ln.Addr().(*net.TCPAddr).Port)
    96  }
    97  
    98  func TestListenAndServeStats(t *testing.T) {
    99  	testIns := "testInstance"
   100  	ctx, cancelFunc := context.WithCancel(context.Background())
   101  	s, dataChan := testServer(ctx, t, testIns, &fakeLameduckLister{})
   102  	defer cancelFunc()
   103  
   104  	urlsAndExpectedResponse := map[string]string{
   105  		"/":                      OK,
   106  		"/instance":              "testInstance",
   107  		"/lameduck":              "false",
   108  		"/healthcheck":           OK,
   109  		"/metadata?var=instance": testIns,
   110  		"/metadata?var=xyz":      "'xyz' not found\n",
   111  	}
   112  	for url, expectedResponse := range urlsAndExpectedResponse {
   113  		if response, _ := get(t, s.ln, url); response != expectedResponse {
   114  			t.Errorf("Didn't get the expected response for URL '%s'. Got: %s, Expected: %s", url, response, expectedResponse)
   115  		}
   116  	}
   117  	// Sleep for the export interval and a second extra to allow for the stats to
   118  	// come in.
   119  	time.Sleep(s.statsInterval)
   120  	time.Sleep(time.Second)
   121  
   122  	// Build a map of expected URL stats
   123  	expectedURLStats := make(map[string]int64)
   124  	for url := range urlsAndExpectedResponse {
   125  		url = strings.Split(url, "?")[0]
   126  		expectedURLStats[url]++
   127  	}
   128  	if len(dataChan) != 1 {
   129  		t.Errorf("Wrong number of stats on the stats channel. Got: %d, Expected: %d", len(dataChan), 1)
   130  	}
   131  	em := <-dataChan
   132  
   133  	// See if we got stats for the all URLs
   134  	for url, expectedCount := range expectedURLStats {
   135  		url = strings.Split(url, "?")[0]
   136  		count := em.Metric("req").(*metrics.Map).GetKey(url).Int64()
   137  		if count != expectedCount {
   138  			t.Errorf("Didn't get the expected stats for the URL: %s. Got: %d, Expected: %d", url, count, expectedCount)
   139  		}
   140  	}
   141  }
   142  
   143  func TestLameduckingTestInstance(t *testing.T) {
   144  	ctx, cancelFunc := context.WithCancel(context.Background())
   145  	s, _ := testServer(ctx, t, "testInstance", &fakeLameduckLister{})
   146  	defer cancelFunc()
   147  
   148  	if resp, _ := get(t, s.ln, "lameduck"); !strings.Contains(resp, "false") {
   149  		t.Errorf("Didn't get the expected response for the URL '/lameduck'. got: %q, want it to contain: %q", resp, "false")
   150  	}
   151  	if resp, status := get(t, s.ln, "healthcheck"); resp != OK || status != "200 OK" {
   152  		t.Errorf("Didn't get the expected response for the URL '/healthcheck'. got: %q, %q , want: %q, %q", resp, status, OK, "200 OK")
   153  	}
   154  
   155  	s.ldLister = &fakeLameduckLister{[]string{"testInstance"}, nil}
   156  
   157  	if resp, _ := get(t, s.ln, "lameduck"); !strings.Contains(resp, "true") {
   158  		t.Errorf("Didn't get the expected response for the URL '/lameduck'. got: %q, want it to contain: %q", resp, "true")
   159  	}
   160  	if _, status := get(t, s.ln, "healthcheck"); status != "503 Service Unavailable" {
   161  		t.Errorf("Didn't get the expected response for the URL '/healthcheck'. got: %q , want: %q", status, "200 OK")
   162  	}
   163  }
   164  
   165  func TestLameduckListerNil(t *testing.T) {
   166  	expectedErrMsg := "not initialized"
   167  
   168  	ctx, cancelFunc := context.WithCancel(context.Background())
   169  	s, _ := testServer(ctx, t, "testInstance", nil)
   170  	defer cancelFunc()
   171  
   172  	if resp, status := get(t, s.ln, "lameduck"); !strings.Contains(resp, expectedErrMsg) || status != "200 OK" {
   173  		t.Errorf("Didn't get the expected response for the URL '/lameduck'. got: %q, %q. want it to contain: %q, %q", resp, status, expectedErrMsg, "200 OK")
   174  	}
   175  	if resp, status := get(t, s.ln, "healthcheck"); resp != OK || status != "200 OK" {
   176  		t.Errorf("Didn't get the expected response for the URL '/healthcheck'. got: %q, %q , want: %q, %q", resp, status, OK, "200 OK")
   177  	}
   178  }