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 }