Browse code

Router tests should not assume access to pod network

Fails when run against a remote cluster.

Clayton Coleman authored on 2016/12/27 05:49:26
Showing 3 changed files
... ...
@@ -3,11 +3,14 @@ package images
3 3
 import (
4 4
 	"fmt"
5 5
 	"net/http"
6
+	"strconv"
7
+	"strings"
6 8
 	"time"
7 9
 
8 10
 	g "github.com/onsi/ginkgo"
9 11
 	o "github.com/onsi/gomega"
10 12
 
13
+	kapi "k8s.io/kubernetes/pkg/api"
11 14
 	"k8s.io/kubernetes/pkg/util/wait"
12 15
 	e2e "k8s.io/kubernetes/test/e2e/framework"
13 16
 
... ...
@@ -32,6 +35,9 @@ var _ = g.Describe("[networking][router] openshift routers", func() {
32 32
 	g.Describe("The HAProxy router", func() {
33 33
 		g.It("should serve the correct routes when scoped to a single namespace and label set", func() {
34 34
 			oc.SetOutputDir(exutil.TestContext.OutputDir)
35
+			ns := oc.KubeFramework().Namespace.Name
36
+			execPodName := exutil.CreateExecPodOrFail(oc.AdminKubeClient().Core(), ns, "execpod")
37
+			defer func() { oc.AdminKubeClient().Core().Pods(ns).Delete(execPodName, kapi.NewDeleteOptions(1)) }()
35 38
 
36 39
 			g.By(fmt.Sprintf("creating a scoped router from a config file %q", configPath))
37 40
 
... ...
@@ -54,28 +60,25 @@ var _ = g.Describe("[networking][router] openshift routers", func() {
54 54
 
55 55
 			g.By("waiting for the healthz endpoint to respond")
56 56
 			healthzURI := fmt.Sprintf("http://%s:1936/healthz", routerIP)
57
-			err = waitForRouterOKResponse(healthzURI, routerIP, 2*time.Minute)
57
+			err = waitForRouterOKResponseExec(ns, execPodName, healthzURI, routerIP, 60*2)
58 58
 			o.Expect(err).NotTo(o.HaveOccurred())
59 59
 
60 60
 			g.By("waiting for the valid route to respond")
61
-			err = waitForRouterOKResponse(routerURL, "first.example.com", 2*time.Minute)
61
+			err = waitForRouterOKResponseExec(ns, execPodName, routerURL, "first.example.com", 60*2)
62 62
 			o.Expect(err).NotTo(o.HaveOccurred())
63 63
 
64 64
 			for _, host := range []string{"second.example.com", "third.example.com"} {
65 65
 				g.By(fmt.Sprintf("checking that %s does not match a route", host))
66
-				req, err := requestViaReverseProxy("GET", routerURL, host)
67
-				o.Expect(err).NotTo(o.HaveOccurred())
68
-				resp, err := http.DefaultClient.Do(req)
66
+				err = expectRouteStatusCodeExec(ns, execPodName, routerURL, host, http.StatusServiceUnavailable)
69 67
 				o.Expect(err).NotTo(o.HaveOccurred())
70
-				resp.Body.Close()
71
-				if resp.StatusCode != http.StatusServiceUnavailable {
72
-					e2e.Failf("should have had a 503 status code for %s", host)
73
-				}
74 68
 			}
75 69
 		})
70
+
76 71
 		g.It("should override the route host with a custom value", func() {
77 72
 			oc.SetOutputDir(exutil.TestContext.OutputDir)
78 73
 			ns := oc.KubeFramework().Namespace.Name
74
+			execPodName := exutil.CreateExecPodOrFail(oc.AdminKubeClient().Core(), ns, "execpod")
75
+			defer func() { oc.AdminKubeClient().Core().Pods(ns).Delete(execPodName, kapi.NewDeleteOptions(1)) }()
79 76
 
80 77
 			g.By(fmt.Sprintf("creating a scoped router from a config file %q", configPath))
81 78
 
... ...
@@ -100,68 +103,91 @@ var _ = g.Describe("[networking][router] openshift routers", func() {
100 100
 
101 101
 			g.By("waiting for the healthz endpoint to respond")
102 102
 			healthzURI := fmt.Sprintf("http://%s:1936/healthz", routerIP)
103
-			err = waitForRouterOKResponse(healthzURI, routerIP, 2*time.Minute)
103
+			err = waitForRouterOKResponseExec(ns, execPodName, healthzURI, routerIP, 60*2)
104 104
 			o.Expect(err).NotTo(o.HaveOccurred())
105 105
 
106
+			oc.KubeFramework().Client.Pods(ns)
107
+
106 108
 			g.By("waiting for the valid route to respond")
107
-			err = waitForRouterOKResponse(routerURL, fmt.Sprintf(pattern, "route-1", ns), 2*time.Minute)
109
+			err = waitForRouterOKResponseExec(ns, execPodName, routerURL, fmt.Sprintf(pattern, "route-1", ns), 60*2)
108 110
 			o.Expect(err).NotTo(o.HaveOccurred())
109 111
 
110 112
 			g.By("checking that the stored domain name does not match a route")
111 113
 			host := "first.example.com"
112
-			req, err := requestViaReverseProxy("GET", routerURL, host)
113
-			o.Expect(err).NotTo(o.HaveOccurred())
114
-			resp, err := http.DefaultClient.Do(req)
114
+			err = expectRouteStatusCodeExec(ns, execPodName, routerURL, host, http.StatusServiceUnavailable)
115 115
 			o.Expect(err).NotTo(o.HaveOccurred())
116
-			resp.Body.Close()
117
-			if resp.StatusCode != http.StatusServiceUnavailable {
118
-				e2e.Failf("should have had a 503 status code for %s", host)
119
-			}
120 116
 
121 117
 			for _, host := range []string{"route-1", "route-2"} {
122 118
 				host = fmt.Sprintf(pattern, host, ns)
123
-				g.By(fmt.Sprintf("checking that %s does not match a route", host))
124
-				req, err := requestViaReverseProxy("GET", routerURL, host)
125
-				o.Expect(err).NotTo(o.HaveOccurred())
126
-				resp, err := http.DefaultClient.Do(req)
119
+				g.By(fmt.Sprintf("checking that %s matches a route", host))
120
+				err = expectRouteStatusCodeExec(ns, execPodName, routerURL, host, http.StatusOK)
127 121
 				o.Expect(err).NotTo(o.HaveOccurred())
128
-				resp.Body.Close()
129
-				if resp.StatusCode != http.StatusOK {
130
-					e2e.Failf("should have had a 200 status code for %s", host)
131
-				}
132 122
 			}
133 123
 		})
134 124
 	})
135 125
 })
136 126
 
137
-func waitForRouterOKResponse(url, host string, timeout time.Duration) error {
138
-	return wait.Poll(time.Second, timeout, func() (bool, error) {
139
-		req, err := requestViaReverseProxy("GET", url, host)
140
-		if err != nil {
141
-			return false, err
142
-		}
143
-		resp, err := http.DefaultClient.Do(req)
144
-		if err != nil {
145
-			return false, nil
146
-		}
147
-		resp.Body.Close()
148
-		if resp.StatusCode == http.StatusServiceUnavailable {
149
-			// not ready yet
150
-			return false, nil
151
-		}
152
-		if resp.StatusCode != http.StatusOK {
153
-			e2e.Logf("unexpected response: %#v", resp.StatusCode)
154
-			return false, nil
155
-		}
156
-		return true, nil
157
-	})
127
+func waitForRouterOKResponseExec(ns, execPodName, url, host string, timeoutSeconds int) error {
128
+	cmd := fmt.Sprintf(`
129
+		set -e
130
+		for i in $(seq 1 %d); do
131
+			code=$( curl -s -o /dev/null -w '%%{http_code}\n' --header 'Host: %s' %q )
132
+			echo $code
133
+			if [[ $code -eq 200 ]]; then
134
+				exit 0
135
+			fi
136
+			if [[ $code -ne 503 ]]; then
137
+				exit 1
138
+			fi
139
+			sleep 1
140
+		done
141
+		`, timeoutSeconds, host, url)
142
+	output, err := e2e.RunHostCmd(ns, execPodName, cmd)
143
+	if err != nil {
144
+		return fmt.Errorf("host command failed: %v\n%s", err, output)
145
+	}
146
+	lines := strings.Split(strings.TrimSpace(output), "\n")
147
+	if lines[len(lines)-1] != "200" {
148
+		return fmt.Errorf("last response from server was not 200:\n%s", output)
149
+	}
150
+	return nil
151
+}
152
+
153
+func expectRouteStatusCodeRepeatedExec(ns, execPodName, url, host string, statusCode int, times int) error {
154
+	cmd := fmt.Sprintf(`
155
+		set -e
156
+		for i in $(seq 1 %d); do
157
+			code=$( curl -s -o /dev/null -w '%%{http_code}\n' --header 'Host: %s' %q )
158
+			echo $code
159
+			if [[ $code -ne %d ]]; then
160
+				exit 1
161
+			fi
162
+		done
163
+		`, times, host, url, statusCode)
164
+	output, err := e2e.RunHostCmd(ns, execPodName, cmd)
165
+	if err != nil {
166
+		return fmt.Errorf("host command failed: %v\n%s", err, output)
167
+	}
168
+	return nil
169
+}
170
+
171
+func expectRouteStatusCodeExec(ns, execPodName, url, host string, statusCode int) error {
172
+	cmd := fmt.Sprintf("curl -s -o /dev/null -w '%%{http_code}' --header 'Host: %s' %q", host, url)
173
+	output, err := e2e.RunHostCmd(ns, execPodName, cmd)
174
+	if err != nil {
175
+		return fmt.Errorf("host command failed: %v\n%s", err, output)
176
+	}
177
+	if output != strconv.Itoa(statusCode) {
178
+		return fmt.Errorf("last response from server was not %d: %s", statusCode, output)
179
+	}
180
+	return nil
158 181
 }
159 182
 
160
-func requestViaReverseProxy(method, url, host string) (*http.Request, error) {
161
-	req, err := http.NewRequest(method, url, nil)
183
+func getAuthenticatedRouteURLViaPod(ns, execPodName, url, host, user, pass string) (string, error) {
184
+	cmd := fmt.Sprintf("curl -s -u %s:%s --header 'Host: %s' %q", user, pass, host, url)
185
+	output, err := e2e.RunHostCmd(ns, execPodName, cmd)
162 186
 	if err != nil {
163
-		return nil, err
187
+		return "", fmt.Errorf("host command failed: %v\n%s", err, output)
164 188
 	}
165
-	req.Host = host
166
-	return req, nil
189
+	return output, nil
167 190
 }
... ...
@@ -3,7 +3,6 @@ package images
3 3
 import (
4 4
 	"encoding/csv"
5 5
 	"fmt"
6
-	"io/ioutil"
7 6
 	"net/http"
8 7
 	"strconv"
9 8
 	"strings"
... ...
@@ -12,6 +11,7 @@ import (
12 12
 	g "github.com/onsi/ginkgo"
13 13
 	o "github.com/onsi/gomega"
14 14
 
15
+	kapi "k8s.io/kubernetes/pkg/api"
15 16
 	"k8s.io/kubernetes/pkg/util/wait"
16 17
 	e2e "k8s.io/kubernetes/test/e2e/framework"
17 18
 
... ...
@@ -35,8 +35,10 @@ var _ = g.Describe("[networking][router] weighted openshift router", func() {
35 35
 
36 36
 	g.Describe("The HAProxy router", func() {
37 37
 		g.It("should appropriately serve a route that points to two services", func() {
38
-
39 38
 			oc.SetOutputDir(exutil.TestContext.OutputDir)
39
+			ns := oc.KubeFramework().Namespace.Name
40
+			execPodName := exutil.CreateExecPodOrFail(oc.AdminKubeClient().Core(), ns, "execpod")
41
+			defer func() { oc.AdminKubeClient().Core().Pods(ns).Delete(execPodName, kapi.NewDeleteOptions(1)) }()
40 42
 
41 43
 			g.By(fmt.Sprintf("creating a weighted router from a config file %q", configPath))
42 44
 
... ...
@@ -60,34 +62,30 @@ var _ = g.Describe("[networking][router] weighted openshift router", func() {
60 60
 
61 61
 			g.By("waiting for the healthz endpoint to respond")
62 62
 			healthzURI := fmt.Sprintf("http://%s:1936/healthz", routerIP)
63
-			err = waitForRouterOKResponse(healthzURI, routerIP, 2*time.Minute)
63
+			err = waitForRouterOKResponseExec(ns, execPodName, healthzURI, routerIP, 60*2)
64 64
 			o.Expect(err).NotTo(o.HaveOccurred())
65 65
 
66 66
 			host := "weighted.example.com"
67
-			g.By(fmt.Sprintf("checking that 10 requests go through successfully"))
68
-			for i := 1; i <= 100; i++ {
69
-				err = waitForRouterOKResponse(routerURL, "weighted.example.com", 1*time.Minute)
70
-				o.Expect(err).NotTo(o.HaveOccurred())
71
-			}
72
-			g.By(fmt.Sprintf("checking that stats can be obtained successfully"))
73
-			statsURL := fmt.Sprintf("http://%s:1936/;csv", routerIP)
74
-			req, err := requestViaReverseProxy("GET", statsURL, host)
75
-			req.SetBasicAuth("admin", "password")
76
-			resp, err := http.DefaultClient.Do(req)
67
+			times := 100
68
+			g.By(fmt.Sprintf("checking that %d requests go through successfully", times))
69
+			// wait for the request to stabilize
70
+			err = waitForRouterOKResponseExec(ns, execPodName, routerURL, "weighted.example.com", 60*2)
77 71
 			o.Expect(err).NotTo(o.HaveOccurred())
78
-			if resp.StatusCode != http.StatusOK {
79
-				e2e.Failf("unexpected response: %#v", resp.StatusCode)
80
-			}
81
-
82
-			g.By(fmt.Sprintf("checking that weights are respected by the router"))
83
-			stats, err := ioutil.ReadAll(resp.Body)
84
-			defer resp.Body.Close()
85
-			trafficValues, err := parseStats(string(stats), "weightedroute", 7)
72
+			// all requests should now succeed
73
+			err = expectRouteStatusCodeRepeatedExec(ns, execPodName, routerURL, "weighted.example.com", http.StatusOK, times)
86 74
 			o.Expect(err).NotTo(o.HaveOccurred())
87 75
 
88
-			if len(trafficValues) != 2 {
89
-				e2e.Failf("Expected 2 weighted backends for incoming traffic, found %d", len(trafficValues))
90
-			}
76
+			g.By(fmt.Sprintf("checking that there are two weighted backends in the router stats"))
77
+			var trafficValues []string
78
+			err = wait.PollImmediate(100*time.Millisecond, 2*time.Minute, func() (bool, error) {
79
+				statsURL := fmt.Sprintf("http://%s:1936/;csv", routerIP)
80
+				stats, err := getAuthenticatedRouteURLViaPod(ns, execPodName, statsURL, host, "admin", "password")
81
+				o.Expect(err).NotTo(o.HaveOccurred())
82
+				trafficValues, err = parseStats(stats, "weightedroute", 7)
83
+				o.Expect(err).NotTo(o.HaveOccurred())
84
+				return len(trafficValues) == 2, nil
85
+			})
86
+			o.Expect(err).NotTo(o.HaveOccurred())
91 87
 
92 88
 			trafficEP1, err := strconv.Atoi(trafficValues[0])
93 89
 			o.Expect(err).NotTo(o.HaveOccurred())
... ...
@@ -101,12 +99,8 @@ var _ = g.Describe("[networking][router] weighted openshift router", func() {
101 101
 
102 102
 			g.By(fmt.Sprintf("checking that zero weights are also respected by the router"))
103 103
 			host = "zeroweight.example.com"
104
-			req, _ = requestViaReverseProxy("GET", routerURL, host)
105
-			resp, err = http.DefaultClient.Do(req)
104
+			err = expectRouteStatusCodeExec(ns, execPodName, routerURL, host, http.StatusServiceUnavailable)
106 105
 			o.Expect(err).NotTo(o.HaveOccurred())
107
-			if resp.StatusCode != http.StatusServiceUnavailable {
108
-				e2e.Failf("Expected zero weighted route to return a 503, but got %v", resp.StatusCode)
109
-			}
110 106
 		})
111 107
 	})
112 108
 })
... ...
@@ -33,6 +33,7 @@ import (
33 33
 	"k8s.io/kubernetes/pkg/util/sets"
34 34
 	"k8s.io/kubernetes/pkg/util/uuid"
35 35
 	"k8s.io/kubernetes/pkg/util/wait"
36
+	"k8s.io/kubernetes/test/e2e/framework"
36 37
 
37 38
 	buildapi "github.com/openshift/origin/pkg/build/api"
38 39
 	"github.com/openshift/origin/pkg/client"
... ...
@@ -1216,3 +1217,43 @@ func GetPodForImage(dockerImageReference string) *kapi.Pod {
1216 1216
 		Image: dockerImageReference,
1217 1217
 	})
1218 1218
 }
1219
+
1220
+// CreateExecPodOrFail creates a simple busybox pod in a sleep loop used as a
1221
+// vessel for kubectl exec commands.
1222
+// Returns the name of the created pod.
1223
+// TODO: expose upstream
1224
+func CreateExecPodOrFail(client kcoreclient.CoreInterface, ns, name string) string {
1225
+	framework.Logf("Creating new exec pod")
1226
+	execPod := framework.NewHostExecPodSpec(ns, name)
1227
+	created, err := client.Pods(ns).Create(execPod)
1228
+	o.Expect(err).NotTo(o.HaveOccurred())
1229
+	err = wait.PollImmediate(framework.Poll, 5*time.Minute, func() (bool, error) {
1230
+		retrievedPod, err := client.Pods(execPod.Namespace).Get(created.Name)
1231
+		if err != nil {
1232
+			return false, nil
1233
+		}
1234
+		return retrievedPod.Status.Phase == kapi.PodRunning, nil
1235
+	})
1236
+	o.Expect(err).NotTo(o.HaveOccurred())
1237
+	return created.Name
1238
+}
1239
+
1240
+// CreateExecPodOnNode launches a exec pod in the given namespace and node
1241
+// waits until it's Running, created pod name would be returned
1242
+// TODO: expose upstream
1243
+func CreateExecPodOnNode(client kcoreclient.CoreInterface, ns, nodeName, name string) string {
1244
+	framework.Logf("Creating exec pod %q in namespace %q", name, ns)
1245
+	execPod := framework.NewHostExecPodSpec(ns, name)
1246
+	execPod.Spec.NodeName = nodeName
1247
+	created, err := client.Pods(ns).Create(execPod)
1248
+	o.Expect(err).NotTo(o.HaveOccurred())
1249
+	err = wait.PollImmediate(framework.Poll, 5*time.Minute, func() (bool, error) {
1250
+		retrievedPod, err := client.Pods(execPod.Namespace).Get(created.Name)
1251
+		if err != nil {
1252
+			return false, nil
1253
+		}
1254
+		return retrievedPod.Status.Phase == kapi.PodRunning, nil
1255
+	})
1256
+	o.Expect(err).NotTo(o.HaveOccurred())
1257
+	return created.Name
1258
+}