Exemple #1
0
// TestRouterHealthzEndpoint tests that the router is listening on and
// exposing the /healthz endpoint for the default haproxy router image.
func TestRouterHealthzEndpoint(t *testing.T) {
	testCases := []struct {
		name string
		port int
	}{
		{
			name: "stats port enabled",
			port: statsPort,
		},
		{
			name: "stats port disabled",
			port: 0,
		},
		{
			name: "custom stats port",
			port: 6391,
		},
	}

	fakeMasterAndPod := tr.NewTestHttpService()
	err := fakeMasterAndPod.Start()
	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}
	defer fakeMasterAndPod.Stop()

	validateServer(fakeMasterAndPod, t)

	dockerCli, err := testutil.NewDockerClient()
	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	for _, tc := range testCases {
		routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr, tc.port, 0)
		if err != nil {
			t.Fatalf("Test with %q error starting container %s : %v", tc.name, getRouterImage(), err)
		}
		defer cleanUp(dockerCli, routerId)

		time.Sleep(time.Second * 10)

		host := "127.0.0.1"
		port := tc.port
		if tc.port == 0 {
			port = statsPort
		}

		uri := fmt.Sprintf("%s:%d/healthz", host, port)
		resp, err := getRoute(uri, host, "http", nil, "")

		if err != nil {
			t.Errorf("Test with %q unable to verify response: %v", tc.name, err)
		}

		if len(resp) < 1 {
			t.Errorf("TestRouterHealthzEndpoint with %q failed! No Response body.", tc.name)
		}
	}
}
Exemple #2
0
// TestRouterStatsPort tests that the router is listening on and
// exposing statistics for the default haproxy router image.
func TestRouterStatsPort(t *testing.T) {
	fakeMasterAndPod := tr.NewTestHttpService()
	err := fakeMasterAndPod.Start()
	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}
	defer fakeMasterAndPod.Stop()

	validateServer(fakeMasterAndPod, t)

	dockerCli, err := testutil.NewDockerClient()
	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr, statsPort, 1)
	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}
	defer cleanUp(t, dockerCli, routerId)

	waitForRouterToBecomeAvailable("127.0.0.1", statsPort)

	statsHostPort := fmt.Sprintf("%s:%d", "127.0.0.1", statsPort)
	creds := fmt.Sprintf("%s:%s", statsUser, statsPassword)
	auth := fmt.Sprintf("Basic: %s", base64.StdEncoding.EncodeToString([]byte(creds)))
	headers := map[string]string{"Authorization": auth}

	if err := waitForRoute(statsHostPort, statsHostPort, "http", headers, ""); err != ErrUnauthenticated {
		t.Fatalf("Unable to verify response: %v", err)
	}
}
Exemple #3
0
// TestRouterStatsPort tests that the router is listening on and
// exposing statistics for the default haproxy router image.
func TestRouterStatsPort(t *testing.T) {
	fakeMasterAndPod := tr.NewTestHttpService()
	err := fakeMasterAndPod.Start()
	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}
	defer fakeMasterAndPod.Stop()

	validateServer(fakeMasterAndPod, t)

	dockerCli, err := testutil.NewDockerClient()
	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr, statsPort, 0)
	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}
	defer cleanUp(dockerCli, routerId)

	time.Sleep(time.Second * 10)

	statsHostPort := fmt.Sprintf("%s:%d", "127.0.0.1", statsPort)
	creds := fmt.Sprintf("%s:%s", statsUser, statsPassword)
	auth := fmt.Sprintf("Basic: %s", base64.StdEncoding.EncodeToString([]byte(creds)))
	headers := map[string]string{"Authorization": auth}

	resp, err := getRoute(statsHostPort, statsHostPort, "http", headers, "")

	if err != nil {
		t.Errorf("Unable to verify response: %v", err)
	}

	if len(resp) < 1 {
		t.Errorf("TestRouterStatsPort failed! No Response body.")
	}
}
Exemple #4
0
// TestRouter is the table based test for routers.  It will initialize a fake master/client and expect to deploy
// a router image in docker.  It then sends watch events through the simulator and makes http client requests that
// should go through the deployed router and return data from the client simulator.
func TestRouter(t *testing.T) {
	//create a server which will act as a user deployed application that
	//serves http and https as well as act as a master to simulate watches
	fakeMasterAndPod := tr.NewTestHttpService()
	defer fakeMasterAndPod.Stop()

	err := fakeMasterAndPod.Start()
	validateServer(fakeMasterAndPod, t)

	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}

	//deploy router docker container
	dockerCli, err := testutil.NewDockerClient()

	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr)

	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}

	defer cleanUp(dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}
	httpsEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpsAddr)
	if err != nil {
		t.Fatalf("Couldn't get https endpoint: %v", err)
	}

	//run through test cases now that environment is set up
	testCases := []struct {
		name              string
		serviceName       string
		endpoints         []kapi.EndpointSubset
		routeAlias        string
		routePath         string
		endpointEventType watch.EventType
		routeEventType    watch.EventType
		protocol          string
		expectedResponse  string
		routeTLS          *routeapi.TLSConfig
		routerUrl         string
	}{
		{
			name:              "non-secure",
			serviceName:       "example",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example-unsecure.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "http",
			expectedResponse:  tr.HelloPod,
			routeTLS:          nil,
			routerUrl:         "0.0.0.0",
		},
		{
			name:              "non-secure-path",
			serviceName:       "example-path",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example-unsecure.com",
			routePath:         "/test",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "http",
			expectedResponse:  tr.HelloPodPath,
			routeTLS:          nil,
			routerUrl:         "0.0.0.0/test",
		},
		{
			name:              "edge termination",
			serviceName:       "example-edge",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example-edge.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPod,
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0",
		},
		{
			name:              "edge termination path",
			serviceName:       "example-edge-path",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example-edge.com",
			routePath:         "/test",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodPath,
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0/test",
		},
		{
			name:              "reencrypt",
			serviceName:       "example-reencrypt",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example-reencrypt.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination:              routeapi.TLSTerminationReencrypt,
				Certificate:              tr.ExampleCert,
				Key:                      tr.ExampleKey,
				CACertificate:            tr.ExampleCACert,
				DestinationCACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0",
		},
		{
			name:              "reencrypt path",
			serviceName:       "example-reencrypt-path",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example-reencrypt.com",
			routePath:         "/test",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodPathSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination:              routeapi.TLSTerminationReencrypt,
				Certificate:              tr.ExampleCert,
				Key:                      tr.ExampleKey,
				CACertificate:            tr.ExampleCACert,
				DestinationCACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0/test",
		},
		{
			name:              "passthrough termination",
			serviceName:       "example-passthrough",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example-passthrough.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination: routeapi.TLSTerminationPassthrough,
			},
			routerUrl: "0.0.0.0",
		},
		{
			name:              "websocket unsecure",
			serviceName:       "websocket-unsecure",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "0.0.0.0:80",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "ws",
			expectedResponse:  "hello-websocket-unsecure",
			routerUrl:         "0.0.0.0:80/echo",
		},
		{
			name:              "ws edge termination",
			serviceName:       "websocket-edge",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "0.0.0.0:443",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "wss",
			expectedResponse:  "hello-websocket-edge",
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0:443/echo",
		},
		{
			name:              "ws passthrough termination",
			serviceName:       "websocket-passthrough",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "0.0.0.0:443",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "wss",
			expectedResponse:  "hello-websocket-passthrough",
			routeTLS: &routeapi.TLSConfig{
				Termination: routeapi.TLSTerminationPassthrough,
			},
			routerUrl: "0.0.0.0:443/echo",
		},
	}

	ns := "rotorouter"
	for _, tc := range testCases {
		//simulate the events
		endpointEvent := &watch.Event{
			Type: tc.endpointEventType,

			Object: &kapi.Endpoints{
				ObjectMeta: kapi.ObjectMeta{
					Name:      tc.serviceName,
					Namespace: ns,
				},
				Subsets: tc.endpoints,
			},
		}

		routeEvent := &watch.Event{
			Type: tc.routeEventType,
			Object: &routeapi.Route{
				ObjectMeta: kapi.ObjectMeta{
					Name:      tc.serviceName,
					Namespace: ns,
				},
				Host:        tc.routeAlias,
				Path:        tc.routePath,
				ServiceName: tc.serviceName,
				TLS:         tc.routeTLS,
			},
		}

		fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
		fakeMasterAndPod.RouteChannel <- eventString(routeEvent)

		for i := 0; i < tcRetries; i++ {
			//wait for router to pick up configs
			time.Sleep(time.Second * tcWaitSeconds)
			//now verify the route with an http client
			resp, err := getRoute(tc.routerUrl, tc.routeAlias, tc.protocol, tc.expectedResponse)

			if err != nil {
				if i != 2 {
					continue
				}
				t.Errorf("Unable to verify response: %v", err)
			}

			if resp != tc.expectedResponse {
				t.Errorf("TC %s failed! Response body %v did not match expected %v", tc.name, resp, tc.expectedResponse)
			} else {
				//good to go, stop trying
				break
			}
		}

		//clean up
		routeEvent.Type = watch.Deleted
		endpointEvent.Type = watch.Deleted

		fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
		fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	}
}
Exemple #5
0
// TestRouterDuplications ensures that the router implementation is keying correctly and resolving routes that may be
// using the same services with different hosts
func TestRouterDuplications(t *testing.T) {
	fakeMasterAndPod := tr.NewTestHttpService()
	err := fakeMasterAndPod.Start()
	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}
	defer fakeMasterAndPod.Stop()

	validateServer(fakeMasterAndPod, t)

	dockerCli, err := testutil.NewDockerClient()
	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr)
	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}
	defer cleanUp(dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	//create routes
	endpointEvent := &watch.Event{
		Type: watch.Added,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "myService",
				Namespace: "default",
			},
			Subsets: []kapi.EndpointSubset{httpEndpoint},
		},
	}
	exampleRouteEvent := &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "example",
				Namespace: "default",
			},
			Host:        "www.example.com",
			ServiceName: "myService",
		},
	}
	example2RouteEvent := &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "example2",
				Namespace: "default",
			},
			Host:        "www.example2.com",
			ServiceName: "myService",
		},
	}

	fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
	fakeMasterAndPod.RouteChannel <- eventString(exampleRouteEvent)
	fakeMasterAndPod.RouteChannel <- eventString(example2RouteEvent)

	var examplePass, example2Pass bool
	var exampleResp, example2Resp string
	for i := 0; i < tcRetries; i++ {
		//ensure you can curl both
		examplePass, exampleResp = isValidRoute("0.0.0.0", "www.example.com", "http", tr.HelloPod)
		example2Pass, example2Resp = isValidRoute("0.0.0.0", "www.example2.com", "http", tr.HelloPod)

		if examplePass && example2Pass {
			break
		}
		//not valid yet, give it some more time before failing
		time.Sleep(time.Second * tcWaitSeconds)
	}

	if !examplePass || !example2Pass {
		t.Errorf("Unable to validate both routes in a duplicate service scenario.  Resp 1: %s, Resp 2: %s", exampleResp, example2Resp)
	}
}
Exemple #6
0
// TestRouterPathSpecificity tests that the router is matching routes from most specific to least when using
// a combination of path AND host based routes.  It also ensures that a host based route still allows path based
// matches via the host header.
//
// For example, the http server simulator acts as if it has a directory structure like:
// /var/www
//         index.html (Hello Pod)
//         /test
//              index.html (Hello Pod Path)
//
// With just a path based route for www.example.com/test I should get Hello Pod Path for a curl to www.example.com/test
// A curl to www.example.com should fall through to the default handlers.  In the test environment it will fall through
// to a call to 0.0.0.0:8080 which is the master simulator
//
// If a host based route for www.example.com is added into the mix I should then be able to curl www.example.com and get
// Hello Pod and still be able to curl www.example.com/test and get Hello Pod Path
//
// If the path based route is deleted I should still be able to curl both routes successfully using the host based path
func TestRouterPathSpecificity(t *testing.T) {
	fakeMasterAndPod := tr.NewTestHttpService()
	err := fakeMasterAndPod.Start()
	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}
	defer fakeMasterAndPod.Stop()

	validateServer(fakeMasterAndPod, t)

	dockerCli, err := testutil.NewDockerClient()
	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr)
	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}
	defer cleanUp(dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	//create path based route
	endpointEvent := &watch.Event{
		Type: watch.Added,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "myService",
				Namespace: "default",
			},
			Subsets: []kapi.EndpointSubset{httpEndpoint},
		},
	}
	routeEvent := &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "path",
				Namespace: "default",
			},
			Host:        "www.example.com",
			Path:        "/test",
			ServiceName: "myService",
		},
	}

	fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	//ensure you can curl path but not main host
	validateRoute("0.0.0.0/test", "www.example.com", "http", tr.HelloPodPath, t)
	//should fall through to the default backend which is 127.0.0.1:8080 where the test server is simulating a master
	validateRoute("0.0.0.0", "www.example.com", "http", tr.HelloMaster, t)

	//create host based route
	routeEvent = &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "host",
				Namespace: "default",
			},
			Host:        "www.example.com",
			ServiceName: "myService",
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	//ensure you can curl path and host
	validateRoute("0.0.0.0/test", "www.example.com", "http", tr.HelloPodPath, t)
	validateRoute("0.0.0.0", "www.example.com", "http", tr.HelloPod, t)

	//delete path based route
	routeEvent = &watch.Event{
		Type: watch.Deleted,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "path",
				Namespace: "default",
			},
			Host:        "www.example.com",
			Path:        "/test",
			ServiceName: "myService",
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	//ensure you can still curl path and host
	validateRoute("0.0.0.0/test", "www.example.com", "http", tr.HelloPodPath, t)
	validateRoute("0.0.0.0", "www.example.com", "http", tr.HelloPod, t)
}
Exemple #7
0
// TestRouterPathSpecificity tests that the router is matching routes from most specific to least when using
// a combination of path AND host based routes.  It also ensures that a host based route still allows path based
// matches via the host header.
//
// For example, the http server simulator acts as if it has a directory structure like:
// /var/www
//         index.html (Hello Pod)
//         /test
//              index.html (Hello Pod Path)
//
// With just a path based route for www.example.com/test I should get Hello Pod Path for a curl to www.example.com/test
// A curl to www.example.com should fall through to the default handlers.  In the test environment it will fall through
// to a call to 0.0.0.0:8080 which is the master simulator
//
// If a host based route for www.example.com is added into the mix I should then be able to curl www.example.com and get
// Hello Pod and still be able to curl www.example.com/test and get Hello Pod Path
//
// If the path based route is deleted I should still be able to curl both routes successfully using the host based path
func TestRouterPathSpecificity(t *testing.T) {
	fakeMasterAndPod := tr.NewTestHttpService()
	err := fakeMasterAndPod.Start()
	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}
	defer fakeMasterAndPod.Stop()

	validateServer(fakeMasterAndPod, t)

	dockerCli, err := testutil.NewDockerClient()
	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr)
	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}
	defer cleanUp(dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	//create path based route
	endpointEvent := &watch.Event{
		Type: watch.Added,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "myService",
				Namespace: "default",
			},
			Subsets: []kapi.EndpointSubset{httpEndpoint},
		},
	}
	routeEvent := &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "path",
				Namespace: "default",
			},
			Host:        "www.example.com",
			Path:        "/test",
			ServiceName: "myService",
		},
	}

	fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	//ensure you can curl path but not main host
	validateRoute("0.0.0.0/test", "www.example.com", "http", tr.HelloPodPath, t)
	//should fall through to the default backend and get a 503.
	resp, err := getRoute("0.0.0.0", "www.example.com", "http", "")
	if err != nil {
		t.Fatalf("Error getting route to default backend: %v", err)
	}

	// We can get back an empty response or a 503 page. A better check
	// here would be to verify the response code is 503 but that needs
	// getRoute + wrappers around that to change.
	if resp != "" && !strings.Contains(resp, "<h1>503 Service Unavailable</h1>") {
		t.Fatalf("Expected a 503 service unavailable got response :%v:", resp)
	}

	//create host based route
	routeEvent = &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "host",
				Namespace: "default",
			},
			Host:        "www.example.com",
			ServiceName: "myService",
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	//ensure you can curl path and host
	validateRoute("0.0.0.0/test", "www.example.com", "http", tr.HelloPodPath, t)
	validateRoute("0.0.0.0", "www.example.com", "http", tr.HelloPod, t)

	//delete path based route
	routeEvent = &watch.Event{
		Type: watch.Deleted,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "path",
				Namespace: "default",
			},
			Host:        "www.example.com",
			Path:        "/test",
			ServiceName: "myService",
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	//ensure you can still curl path and host
	validateRoute("0.0.0.0/test", "www.example.com", "http", tr.HelloPodPath, t)
	validateRoute("0.0.0.0", "www.example.com", "http", tr.HelloPod, t)
}
Exemple #8
0
// TestRouterServiceUnavailable tests that the router returns valid service
// unavailable error pages with appropriate HTTP headers.`
func TestRouterServiceUnavailable(t *testing.T) {
	fakeMasterAndPod := tr.NewTestHttpService()
	err := fakeMasterAndPod.Start()
	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}
	defer fakeMasterAndPod.Stop()

	validateServer(fakeMasterAndPod, t)

	dockerCli, err := testutil.NewDockerClient()
	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr, statsPort, 1)
	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}
	defer cleanUp(t, dockerCli, routerId)

	waitForRouterToBecomeAvailable("127.0.0.1", statsPort)

	schemes := []string{"http", "https"}
	for _, scheme := range schemes {
		uri := fmt.Sprintf("%s://%s", scheme, getRouteAddress())
		hostAlias := fmt.Sprintf("www.route-%d.test", time.Now().UnixNano())
		var tlsConfig *tls.Config
		if scheme == "https" {
			tlsConfig = &tls.Config{
				InsecureSkipVerify: true,
				ServerName:         hostAlias,
			}
		}

		httpClient := &http.Client{
			Transport: knet.SetTransportDefaults(&http.Transport{
				TLSClientConfig: tlsConfig,
			}),
		}
		req, err := http.NewRequest("GET", uri, nil)
		if err != nil {
			t.Fatalf("Error creating %s request : %v", scheme, err)
		}

		req.Host = hostAlias
		resp, err := httpClient.Do(req)
		if err != nil {
			t.Fatalf("Error dispatching %s request : %v", scheme, err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != 503 {
			t.Fatalf("Router %s response error, got %v expected 503.", scheme, resp.StatusCode)
		}

		headerNames := []string{"Pragma", "Cache-Control"}
		for _, k := range headerNames {
			value := resp.Header.Get(k)
			if len(value) == 0 {
				t.Errorf("Router %s response empty/no header %q",
					scheme, k)
			}

			directive := "no-cache"
			if !strings.Contains(value, directive) {
				t.Errorf("Router %s response header %q missing %s response directive",
					scheme, k, directive)
			}
		}

		respBody, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Errorf("Unable to verify router %s response: %v",
				scheme, err)
		}
		if len(respBody) < 1 {
			t.Errorf("Router %s response body was empty!", scheme)
		}
	}
}
Exemple #9
0
// TestRouterDuplications ensures that the router implementation is keying correctly and resolving routes that may be
// using the same services with different hosts
func TestRouterDuplications(t *testing.T) {
	fakeMasterAndPod := tr.NewTestHttpService()
	err := fakeMasterAndPod.Start()
	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}
	defer fakeMasterAndPod.Stop()

	validateServer(fakeMasterAndPod, t)

	dockerCli, err := testutil.NewDockerClient()
	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr, statsPort, 1)
	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}
	defer cleanUp(t, dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	waitForRouterToBecomeAvailable("127.0.0.1", statsPort)

	//create routes
	endpointEvent := &watch.Event{
		Type: watch.Added,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "myService",
				Namespace: "default",
			},
			Subsets: []kapi.EndpointSubset{httpEndpoint},
		},
	}
	exampleRouteEvent := &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "example",
				Namespace: "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				To: routeapi.RouteTargetReference{
					Name: "myService",
				},
			},
		},
	}
	example2RouteEvent := &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "example2",
				Namespace: "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example2.com",
				To: routeapi.RouteTargetReference{
					Name: "myService",
				},
			},
		},
	}

	sendTimeout(t, fakeMasterAndPod.EndpointChannel, eventString(endpointEvent), 30*time.Second)
	sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(exampleRouteEvent), 30*time.Second)
	sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(example2RouteEvent), 30*time.Second)

	routeAddress := getRouteAddress()

	//ensure you can curl both
	err1 := waitForRoute(routeAddress, "www.example.com", "http", nil, tr.HelloPod)
	err2 := waitForRoute(routeAddress, "www.example2.com", "http", nil, tr.HelloPod)

	if err1 != nil || err2 != nil {
		t.Errorf("Unable to validate both routes in a duplicate service scenario.  Resp 1: %s, Resp 2: %s", err1, err2)
	}

	// Clean up the endpoint and routes.
	example2RouteCleanupEvent := &watch.Event{
		Type: watch.Deleted,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "example2",
				Namespace: "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example2.com",
				To: routeapi.RouteTargetReference{
					Name: "myService",
				},
			},
		},
	}
	sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(example2RouteCleanupEvent), 30*time.Second)
	exampleRouteCleanupEvent := &watch.Event{
		Type: watch.Deleted,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "example",
				Namespace: "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				To: routeapi.RouteTargetReference{
					Name: "myService",
				},
			},
		},
	}
	sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(exampleRouteCleanupEvent), 30*time.Second)
	endpointCleanupEvent := &watch.Event{
		Type: watch.Modified,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "myService",
				Namespace: "default",
			},
			Subsets: []kapi.EndpointSubset{},
		},
	}
	sendTimeout(t, fakeMasterAndPod.EndpointChannel, eventString(endpointCleanupEvent), 30*time.Second)
}
Exemple #10
0
// TestRouter is the table based test for routers.  It will initialize a fake master/client and expect to deploy
// a router image in docker.  It then sends watch events through the simulator and makes http client requests that
// should go through the deployed router and return data from the client simulator.
func TestRouter(t *testing.T) {
	//create a server which will act as a user deployed application that
	//serves http and https as well as act as a master to simulate watches
	fakeMasterAndPod := tr.NewTestHttpService()
	defer fakeMasterAndPod.Stop()

	err := fakeMasterAndPod.Start()
	validateServer(fakeMasterAndPod, t)

	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}

	//deploy router docker container
	dockerCli, err := testutil.NewDockerClient()

	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr, statsPort, 1)

	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}

	defer cleanUp(t, dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}
	httpsEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpsAddr)
	if err != nil {
		t.Fatalf("Couldn't get https endpoint: %v", err)
	}
	alternateHttpEndpoint, err := getEndpoint(fakeMasterAndPod.AlternatePodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	routeAddress := getRouteAddress()
	routeTestAddress := fmt.Sprintf("%s/test", routeAddress)

	//run through test cases now that environment is set up
	testCases := []struct {
		name              string
		serviceName       string
		endpoints         []kapi.EndpointSubset
		routeAlias        string
		routePath         string
		endpointEventType watch.EventType
		routeEventType    watch.EventType
		protocol          string
		expectedResponse  string
		routeTLS          *routeapi.TLSConfig
		routerUrl         string
		preferredPort     *routeapi.RoutePort
	}{
		{
			name:              "non-secure",
			serviceName:       "example",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example-unsecure.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "http",
			expectedResponse:  tr.HelloPod,
			routeTLS:          nil,
			routerUrl:         routeAddress,
		},
		{
			name:              "non-secure-path",
			serviceName:       "example-path",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example-unsecure.com",
			routePath:         "/test",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "http",
			expectedResponse:  tr.HelloPodPath,
			routeTLS:          nil,
			routerUrl:         routeTestAddress,
		},
		{
			name:              "preferred-port",
			serviceName:       "example-preferred-port",
			endpoints:         []kapi.EndpointSubset{alternateHttpEndpoint, httpEndpoint},
			routeAlias:        "www.example-unsecure.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "http",
			expectedResponse:  tr.HelloPod,
			routeTLS:          nil,
			routerUrl:         routeAddress,
			preferredPort:     &routeapi.RoutePort{TargetPort: intstr.FromInt(8888)},
		},
		{
			name:              "edge termination",
			serviceName:       "example-edge",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPod,
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: routeAddress,
		},
		{
			name:              "edge termination path",
			serviceName:       "example-edge-path",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example.com",
			routePath:         "/test",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodPath,
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: routeTestAddress,
		},
		{
			name:              "reencrypt",
			serviceName:       "example-reencrypt",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination:              routeapi.TLSTerminationReencrypt,
				Certificate:              tr.ExampleCert,
				Key:                      tr.ExampleKey,
				CACertificate:            tr.ExampleCACert,
				DestinationCACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0",
		},
		{
			name:              "reencrypt-destcacert",
			serviceName:       "example-reencrypt-destcacert",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination:              routeapi.TLSTerminationReencrypt,
				DestinationCACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0",
		},
		{
			name:              "reencrypt path",
			serviceName:       "example-reencrypt-path",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example.com",
			routePath:         "/test",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodPathSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination:              routeapi.TLSTerminationReencrypt,
				Certificate:              tr.ExampleCert,
				Key:                      tr.ExampleKey,
				CACertificate:            tr.ExampleCACert,
				DestinationCACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0/test",
		},
		{
			name:              "passthrough termination",
			serviceName:       "example-passthrough",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example-passthrough.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination: routeapi.TLSTerminationPassthrough,
			},
			routerUrl: routeAddress,
		},
		{
			name:              "websocket unsecure",
			serviceName:       "websocket-unsecure",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "ws",
			expectedResponse:  "hello-websocket-unsecure",
			routerUrl:         routeAddress,
		},
		{
			name:              "ws edge termination",
			serviceName:       "websocket-edge",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "wss",
			expectedResponse:  "hello-websocket-edge",
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: routeAddress,
		},
		{
			name:              "ws passthrough termination",
			serviceName:       "websocket-passthrough",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "wss",
			expectedResponse:  "hello-websocket-passthrough",
			routeTLS: &routeapi.TLSConfig{
				Termination: routeapi.TLSTerminationPassthrough,
			},
			routerUrl: routeAddress,
		},
	}

	ns := "rotorouter"
	for _, tc := range testCases {
		// Simulate the events.
		endpointEvent := &watch.Event{
			Type: tc.endpointEventType,

			Object: &kapi.Endpoints{
				ObjectMeta: kapi.ObjectMeta{
					Name:      tc.serviceName,
					Namespace: ns,
				},
				Subsets: tc.endpoints,
			},
		}

		routeEvent := &watch.Event{
			Type: tc.routeEventType,
			Object: &routeapi.Route{
				ObjectMeta: kapi.ObjectMeta{
					Name:      tc.serviceName,
					Namespace: ns,
				},
				Spec: routeapi.RouteSpec{
					Host: tc.routeAlias,
					Path: tc.routePath,
					To: routeapi.RouteTargetReference{
						Name: tc.serviceName,
					},
					TLS: tc.routeTLS,
				},
			},
		}
		if tc.preferredPort != nil {
			routeEvent.Object.(*routeapi.Route).Spec.Port = tc.preferredPort
		}

		sendTimeout(t, fakeMasterAndPod.EndpointChannel, eventString(endpointEvent), 30*time.Second)
		sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(routeEvent), 30*time.Second)

		// Now verify the route with an HTTP client.
		t.Logf("TC %s: url %s alias %s protocol %s", tc.name, tc.routerUrl, tc.routeAlias, tc.protocol)
		if err := waitForRoute(tc.routerUrl, tc.routeAlias, tc.protocol, nil, tc.expectedResponse); err != nil {
			t.Errorf("TC %s failed: %v", tc.name, err)

			// The following is related to the workaround above, q.v.
			if getRouterImage() != defaultRouterImage {
				t.Errorf("You may need to add an entry to /etc/hosts so that the"+
					" hostname of the router (%s) resolves its the IP address, (%s).",
					tc.routeAlias, routeAddress)
			}
			if strings.Contains(err.Error(), "unavailable the entire time") {
				break
			}
		}

		//clean up
		routeEvent.Type = watch.Deleted
		endpointEvent.Type = watch.Modified
		endpoints := endpointEvent.Object.(*kapi.Endpoints)
		endpoints.Subsets = []kapi.EndpointSubset{}

		sendTimeout(t, fakeMasterAndPod.EndpointChannel, eventString(endpointEvent), 30*time.Second)
		sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(routeEvent), 30*time.Second)
	}
}
Exemple #11
0
// TestRouterPathSpecificity tests that the router is matching routes from most specific to least when using
// a combination of path AND host based routes.  It also ensures that a host based route still allows path based
// matches via the host header.
//
// For example, the http server simulator acts as if it has a directory structure like:
// /var/www
//         index.html (Hello Pod)
//         /test
//              index.html (Hello Pod Path)
//
// With just a path based route for www.example.com/test I should get Hello Pod Path for a curl to www.example.com/test
// A curl to www.example.com should fall through to the default handlers.  In the test environment it will fall through
// to a call to 0.0.0.0:8080 which is the master simulator
//
// If a host based route for www.example.com is added into the mix I should then be able to curl www.example.com and get
// Hello Pod and still be able to curl www.example.com/test and get Hello Pod Path
//
// If the path based route is deleted I should still be able to curl both routes successfully using the host based path
func TestRouterPathSpecificity(t *testing.T) {
	fakeMasterAndPod := tr.NewTestHttpService()
	err := fakeMasterAndPod.Start()
	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}
	defer fakeMasterAndPod.Stop()

	validateServer(fakeMasterAndPod, t)

	dockerCli, err := testutil.NewDockerClient()
	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr, statsPort, 1)
	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}
	defer cleanUp(t, dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	alternateHttpEndpoint, err := getEndpoint(fakeMasterAndPod.AlternatePodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	waitForRouterToBecomeAvailable("127.0.0.1", statsPort)

	now := unversioned.Now()

	protocols := []struct {
		name string
		port string
	}{
		{
			name: "http",
			port: "80",
		},
		{
			name: "https",
			port: "443",
		},
		{
			name: "ws",
			port: "80",
		},
		{
			name: "wss",
			port: "443",
		},
	}

	//create path based route
	endpointEvent := &watch.Event{
		Type: watch.Added,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				CreationTimestamp: now,
				Name:              "myService",
				Namespace:         "default",
			},
			Subsets: []kapi.EndpointSubset{httpEndpoint},
		},
	}
	routeEvent := &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "path",
				Namespace: "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				Path: "/test",
				To: routeapi.RouteTargetReference{
					Name: "myService",
				},
				TLS: &routeapi.TLSConfig{
					Termination:                   routeapi.TLSTerminationEdge,
					Certificate:                   tr.ExampleCert,
					Key:                           tr.ExampleKey,
					CACertificate:                 tr.ExampleCACert,
					InsecureEdgeTerminationPolicy: routeapi.InsecureEdgeTerminationPolicyAllow,
				},
			},
		},
	}

	routeAddress := getRouteAddress()
	routeTestAddress := fmt.Sprintf("%s/test", routeAddress)

	sendTimeout(t, fakeMasterAndPod.EndpointChannel, eventString(endpointEvent), 30*time.Second)
	sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(routeEvent), 30*time.Second)

	for _, proto := range protocols {
		//ensure you can curl path but not main host
		if err := waitForRoute(routeTestAddress, "www.example.com", proto.name, nil, tr.HelloPodPath); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
		if _, err := getRoute(routeAddress, "www.example.com", proto.name, nil, ""); err != ErrUnavailable {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
		//ensure you can curl path with port in Host header
		if err := waitForRoute(routeTestAddress, "www.example.com:"+proto.port, proto.name, nil, tr.HelloPodPath); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
	}

	//create newer, conflicting path based route
	endpointEvent = &watch.Event{
		Type: watch.Added,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "altService",
				Namespace: "alt",
			},
			Subsets: []kapi.EndpointSubset{alternateHttpEndpoint},
		},
	}
	routeEvent = &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				CreationTimestamp: unversioned.Time{Time: now.Add(time.Hour)},
				Name:              "path",
				Namespace:         "alt",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				Path: "/test",
				To: routeapi.RouteTargetReference{
					Name: "altService",
				},
				TLS: &routeapi.TLSConfig{
					Termination:                   routeapi.TLSTerminationEdge,
					Certificate:                   tr.ExampleCert,
					Key:                           tr.ExampleKey,
					CACertificate:                 tr.ExampleCACert,
					InsecureEdgeTerminationPolicy: routeapi.InsecureEdgeTerminationPolicyAllow,
				},
			},
		},
	}

	sendTimeout(t, fakeMasterAndPod.EndpointChannel, eventString(endpointEvent), 30*time.Second)
	sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(routeEvent), 30*time.Second)

	for _, proto := range protocols {
		if err := waitForRoute(routeTestAddress, "www.example.com", proto.name, nil, tr.HelloPodPath); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
		if err := waitForRoute(routeTestAddress, "www.example.com:"+proto.port, proto.name, nil, tr.HelloPodPath); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
	}

	//create host based route
	routeEvent = &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				CreationTimestamp: now,
				Name:              "host",
				Namespace:         "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				To: routeapi.RouteTargetReference{
					Name: "myService",
				},
				TLS: &routeapi.TLSConfig{
					Termination:                   routeapi.TLSTerminationEdge,
					Certificate:                   tr.ExampleCert,
					Key:                           tr.ExampleKey,
					CACertificate:                 tr.ExampleCACert,
					InsecureEdgeTerminationPolicy: routeapi.InsecureEdgeTerminationPolicyAllow,
				},
			},
		},
	}
	sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(routeEvent), 30*time.Second)

	for _, proto := range protocols {
		//ensure you can curl path and host
		if err := waitForRoute(routeTestAddress, "www.example.com", proto.name, nil, tr.HelloPodPath); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
		if err := waitForRoute(routeAddress, "www.example.com", proto.name, nil, tr.HelloPod); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
		if err := waitForRoute(routeTestAddress, "www.example.com:"+proto.port, proto.name, nil, tr.HelloPodPath); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
	}

	//delete path based route
	routeEvent = &watch.Event{
		Type: watch.Deleted,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "path",
				Namespace: "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				Path: "/test",
				To: routeapi.RouteTargetReference{
					Name: "myService",
				},
				TLS: &routeapi.TLSConfig{
					Termination:                   routeapi.TLSTerminationEdge,
					Certificate:                   tr.ExampleCert,
					Key:                           tr.ExampleKey,
					CACertificate:                 tr.ExampleCACert,
					InsecureEdgeTerminationPolicy: routeapi.InsecureEdgeTerminationPolicyAllow,
				},
			},
		},
	}
	sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(routeEvent), 30*time.Second)

	// Ensure you can still curl path and host.  The host-based route should now
	// handle requests to / as well as requests to /test (or any other path).
	// Note, however, that the host-based route and the host-based route use the
	// same service, and that that service varies its response in accordance with
	// the path, so we still get the tr.HelloPodPath response when we request
	// /test even though we request using routeAddress.
	for _, proto := range protocols {
		if err := waitForRoute(routeTestAddress, "www.example.com", proto.name, nil, tr.HelloPodPath); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
		if err := waitForRoute(routeAddress, "www.example.com", proto.name, nil, tr.HelloPod); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
		if err := waitForRoute(routeTestAddress, "www.example.com:"+proto.port, proto.name, nil, tr.HelloPodPath); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
	}

	// create newer, conflicting host based route that is ignored
	routeEvent = &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				CreationTimestamp: unversioned.Time{Time: now.Add(time.Hour)},
				Name:              "host",
				Namespace:         "alt",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				To: routeapi.RouteTargetReference{
					Name: "altService",
				},
				TLS: &routeapi.TLSConfig{
					Termination:                   routeapi.TLSTerminationEdge,
					Certificate:                   tr.ExampleCert,
					Key:                           tr.ExampleKey,
					CACertificate:                 tr.ExampleCACert,
					InsecureEdgeTerminationPolicy: routeapi.InsecureEdgeTerminationPolicyAllow,
				},
			},
		},
	}
	sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(routeEvent), 30*time.Second)

	for _, proto := range protocols {
		if err := waitForRoute(routeTestAddress, "www.example.com", proto.name, nil, tr.HelloPodPath); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
		if err := waitForRoute(routeAddress, "www.example.com", proto.name, nil, tr.HelloPod); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
		if err := waitForRoute(routeTestAddress, "www.example.com:"+proto.port, proto.name, nil, tr.HelloPodPath); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
	}

	//create old, conflicting host based route which should take over the route
	routeEvent = &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				CreationTimestamp: unversioned.Time{Time: now.Add(-time.Hour)},
				Name:              "host",
				Namespace:         "alt",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				To: routeapi.RouteTargetReference{
					Name: "altService",
				},
				TLS: &routeapi.TLSConfig{
					Termination:                   routeapi.TLSTerminationEdge,
					Certificate:                   tr.ExampleCert,
					Key:                           tr.ExampleKey,
					CACertificate:                 tr.ExampleCACert,
					InsecureEdgeTerminationPolicy: routeapi.InsecureEdgeTerminationPolicyAllow,
				},
			},
		},
	}
	sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(routeEvent), 30*time.Second)

	for _, proto := range protocols {
		if err := waitForRoute(routeTestAddress, "www.example.com", proto.name, nil, tr.HelloPodAlternate); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
		if err := waitForRoute(routeAddress, "www.example.com", proto.name, nil, tr.HelloPodAlternate); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
		if err := waitForRoute(routeTestAddress, "www.example.com:"+proto.port, proto.name, nil, tr.HelloPodAlternate); err != nil {
			t.Fatalf("unexpected response with protocol %s (port %s): %q", proto.name, proto.port, err)
		}
	}

	// Clean up the host-based route and endpoint.
	routeEvent = &watch.Event{
		Type: watch.Deleted,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "host",
				Namespace: "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				To: routeapi.RouteTargetReference{
					Name: "myService",
				},
				TLS: &routeapi.TLSConfig{
					Termination:                   routeapi.TLSTerminationEdge,
					Certificate:                   tr.ExampleCert,
					Key:                           tr.ExampleKey,
					CACertificate:                 tr.ExampleCACert,
					InsecureEdgeTerminationPolicy: routeapi.InsecureEdgeTerminationPolicyAllow,
				},
			},
		},
	}
	sendTimeout(t, fakeMasterAndPod.RouteChannel, eventString(routeEvent), 30*time.Second)
	endpointEvent = &watch.Event{
		Type: watch.Modified,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "myService",
				Namespace: "default",
			},
			Subsets: []kapi.EndpointSubset{},
		},
	}
	sendTimeout(t, fakeMasterAndPod.EndpointChannel, eventString(endpointEvent), 30*time.Second)
}
Exemple #12
0
// TestRouterReloadCoalesce tests that router reloads are coalesced.
func TestRouterReloadCoalesce(t *testing.T) {
	//create a server which will act as a user deployed application that
	//serves http and https as well as act as a master to simulate watches
	fakeMasterAndPod := tr.NewTestHttpService()
	defer fakeMasterAndPod.Stop()

	err := fakeMasterAndPod.Start()
	validateServer(fakeMasterAndPod, t)

	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}

	//deploy router docker container
	dockerCli, err := testutil.NewDockerClient()

	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	reloadInterval := 7

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr, statsPort, reloadInterval)

	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}

	defer cleanUp(t, dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}
	_, err = getEndpoint(fakeMasterAndPod.PodHttpsAddr)
	if err != nil {
		t.Fatalf("Couldn't get https endpoint: %v", err)
	}
	_, err = getEndpoint(fakeMasterAndPod.AlternatePodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	routeAddress := getRouteAddress()

	routeAlias := "www.example.test"
	serviceName := "example"
	endpoints := []kapi.EndpointSubset{httpEndpoint}
	numRoutes := 10

	for i := 1; i <= numRoutes; i++ {
		routeName := fmt.Sprintf("coalesce-route-%v", i)
		routeAlias = fmt.Sprintf("www.example-coalesce-%v.test", i)

		// Send the add events.
		generateTestEvents(t, fakeMasterAndPod, false, serviceName, routeName, routeAlias, endpoints)
	}

	// Wait for the last routeAlias to become available.
	if err := waitForRoute(routeAddress, routeAlias, "http", nil, tr.HelloPod); err != nil {
		t.Fatal(err)
	}

	// And ensure all the coalesce route aliases are available.
	for i := 1; i <= numRoutes; i++ {
		routeAlias := fmt.Sprintf("www.example-coalesce-%v.test", i)
		if err := waitForRoute(routeAddress, routeAlias, "http", nil, tr.HelloPod); err != nil {
			t.Fatalf("Unable to verify response for %q: %v", routeAlias, err)
		}
	}

	for i := 1; i <= numRoutes; i++ {
		routeName := fmt.Sprintf("coalesce-route-%v", i)
		routeAlias = fmt.Sprintf("www.example-coalesce-%v.test", i)

		// Send the cleanup events.
		generateTestEvents(t, fakeMasterAndPod, true, serviceName, routeName, routeAlias, endpoints)
	}

	// Wait for the first routeAlias to become unavailable.
	routeAlias = "www.example-coalesce-1.test"
	if err := wait.Poll(time.Millisecond*100, time.Duration(reloadInterval)*2*time.Second, func() (bool, error) {
		if _, err := getRoute(routeAddress, routeAlias, "http", nil, tr.HelloPod); err != nil {
			return true, nil
		}
		return false, nil
	}); err != nil {
		t.Fatalf("Route did not become unavailable: %v", err)
	}

	// And ensure all the route aliases are gone.
	for i := 1; i <= numRoutes; i++ {
		routeAlias := fmt.Sprintf("www.example-coalesce-%v.test", i)
		if _, err := getRoute(routeAddress, routeAlias, "http", nil, tr.HelloPod); err != ErrUnavailable {
			t.Errorf("Unable to verify route deletion for %q: %+v", routeAlias, err)
		}
	}
}
Exemple #13
0
// TestRouter is the table based test for routers.  It will initialize a fake master/client and expect to deploy
// a router image in docker.  It then sends watch events through the simulator and makes http client requests that
// should go through the deployed router and return data from the client simulator.
func TestRouter(t *testing.T) {
	//create a server which will act as a user deployed application that
	//serves http and https as well as act as a master to simulate watches
	fakeMasterAndPod := tr.NewTestHttpService()
	defer fakeMasterAndPod.Stop()

	err := fakeMasterAndPod.Start()
	validateServer(fakeMasterAndPod, t)

	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}

	//deploy router docker container
	dockerCli, err := testutil.NewDockerClient()

	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr, statsPort, 1)

	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}

	defer cleanUp(dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}
	httpsEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpsAddr)
	if err != nil {
		t.Fatalf("Couldn't get https endpoint: %v", err)
	}
	alternateHttpEndpoint, err := getEndpoint(fakeMasterAndPod.AlternatePodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	routeAddress := getRouteAddress()
	routeTestAddress := fmt.Sprintf("%s/test", routeAddress)
	routerEchoHttpAddress := fmt.Sprintf("%s:80/echo", routeAddress)
	routerEchoHttpsAddress := fmt.Sprintf("%s:443/echo", routeAddress)

	//run through test cases now that environment is set up
	testCases := []struct {
		name              string
		serviceName       string
		endpoints         []kapi.EndpointSubset
		routeAlias        string
		routePath         string
		endpointEventType watch.EventType
		routeEventType    watch.EventType
		protocol          string
		expectedResponse  string
		routeTLS          *routeapi.TLSConfig
		routerUrl         string
		preferredPort     *routeapi.RoutePort
	}{
		{
			name:              "non-secure",
			serviceName:       "example",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example-unsecure.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "http",
			expectedResponse:  tr.HelloPod,
			routeTLS:          nil,
			routerUrl:         routeAddress,
		},
		{
			name:              "non-secure-path",
			serviceName:       "example-path",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example-unsecure.com",
			routePath:         "/test",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "http",
			expectedResponse:  tr.HelloPodPath,
			routeTLS:          nil,
			routerUrl:         routeTestAddress,
		},
		{
			name:              "preferred-port",
			serviceName:       "example-preferred-port",
			endpoints:         []kapi.EndpointSubset{alternateHttpEndpoint, httpEndpoint},
			routeAlias:        "www.example-unsecure.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "http",
			expectedResponse:  tr.HelloPod,
			routeTLS:          nil,
			routerUrl:         routeAddress,
			preferredPort:     &routeapi.RoutePort{TargetPort: intstr.FromInt(8888)},
		},
		{
			name:              "edge termination",
			serviceName:       "example-edge",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPod,
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: routeAddress,
		},
		{
			name:              "edge termination path",
			serviceName:       "example-edge-path",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example.com",
			routePath:         "/test",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodPath,
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: routeTestAddress,
		},
		{
			name:              "reencrypt",
			serviceName:       "example-reencrypt",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination:              routeapi.TLSTerminationReencrypt,
				Certificate:              tr.ExampleCert,
				Key:                      tr.ExampleKey,
				CACertificate:            tr.ExampleCACert,
				DestinationCACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0",
		},
		{
			name:              "reencrypt-destcacert",
			serviceName:       "example-reencrypt-destcacert",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination:              routeapi.TLSTerminationReencrypt,
				DestinationCACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0",
		},
		{
			name:              "reencrypt path",
			serviceName:       "example-reencrypt-path",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example.com",
			routePath:         "/test",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodPathSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination:              routeapi.TLSTerminationReencrypt,
				Certificate:              tr.ExampleCert,
				Key:                      tr.ExampleKey,
				CACertificate:            tr.ExampleCACert,
				DestinationCACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0/test",
		},
		{
			name:              "passthrough termination",
			serviceName:       "example-passthrough",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example-passthrough.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination: routeapi.TLSTerminationPassthrough,
			},
			routerUrl: routeAddress,
		},
		{
			name:              "websocket unsecure",
			serviceName:       "websocket-unsecure",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "ws",
			expectedResponse:  "hello-websocket-unsecure",
			routerUrl:         routerEchoHttpAddress,
		},
		{
			name:              "ws edge termination",
			serviceName:       "websocket-edge",
			endpoints:         []kapi.EndpointSubset{httpEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "wss",
			expectedResponse:  "hello-websocket-edge",
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: routerEchoHttpsAddress,
		},
		{
			name:              "ws passthrough termination",
			serviceName:       "websocket-passthrough",
			endpoints:         []kapi.EndpointSubset{httpsEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "wss",
			expectedResponse:  "hello-websocket-passthrough",
			routeTLS: &routeapi.TLSConfig{
				Termination: routeapi.TLSTerminationPassthrough,
			},
			routerUrl: routerEchoHttpsAddress,
		},
	}

	ns := "rotorouter"
	for _, tc := range testCases {
		// The following is a workaround for the websocket client, which does not
		// allow a "Host" header that is distinct from the address to which the
		// client code attempts to connect—so if we are putting "www.example.com" in
		// the "Host" header, the client will connect to "www.example.com".
		//
		// In the case where we use HAProxy (with the template router), it is
		// possible to use 0.0.0.0, so we can do so as a workaround to get the tests
		// passing with the template router.  In the case of the F5 router though,
		// F5 BIG-IP would reject 0.0.0.0 as an invalid servername, so the only way
		// to make the tests pass with the F5 router is to use a hostname and make
		// that hostname resolve to the F5 BIG-IP host's IP address.
		if getRouterImage() == defaultRouterImage &&
			(tc.protocol == "ws" || tc.protocol == "wss") {
			tc.routeAlias = "0.0.0.0"
		}

		// Simulate the events.
		endpointEvent := &watch.Event{
			Type: tc.endpointEventType,

			Object: &kapi.Endpoints{
				ObjectMeta: kapi.ObjectMeta{
					Name:      tc.serviceName,
					Namespace: ns,
				},
				Subsets: tc.endpoints,
			},
		}

		routeEvent := &watch.Event{
			Type: tc.routeEventType,
			Object: &routeapi.Route{
				ObjectMeta: kapi.ObjectMeta{
					Name:      tc.serviceName,
					Namespace: ns,
				},
				Spec: routeapi.RouteSpec{
					Host: tc.routeAlias,
					Path: tc.routePath,
					To: kapi.ObjectReference{
						Name: tc.serviceName,
					},
					TLS: tc.routeTLS,
				},
			},
		}
		if tc.preferredPort != nil {
			routeEvent.Object.(*routeapi.Route).Spec.Port = tc.preferredPort
		}

		fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
		fakeMasterAndPod.RouteChannel <- eventString(routeEvent)

		// Give the router some time to finish processing events before we connect.
		time.Sleep(time.Second * 5)

		// Now verify the route with an HTTP client.
		resp, err := getRouteWithRetries(t, tc.routerUrl, tc.routeAlias, tc.protocol, nil, tc.expectedResponse)

		if err != nil {
			t.Errorf("Unable to verify response: %v", err)
		}

		if resp != tc.expectedResponse {
			t.Errorf("TC %s failed! Response body %q did not match expected %q", tc.name, resp, tc.expectedResponse)

			// The following is related to the workaround above, q.v.
			if getRouterImage() != defaultRouterImage {
				t.Errorf("You may need to add an entry to /etc/hosts so that the"+
					" hostname of the router (%s) resolves its the IP address, (%s).",
					tc.routeAlias, routeAddress)
			}
		}

		//clean up
		routeEvent.Type = watch.Deleted
		endpointEvent.Type = watch.Modified
		endpoints := endpointEvent.Object.(*kapi.Endpoints)
		endpoints.Subsets = []kapi.EndpointSubset{}

		fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
		fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	}

	// Give the router some time to finish processing events before we kill it.
	time.Sleep(time.Second * 5)
}
Exemple #14
0
// TestRouterPathSpecificity tests that the router is matching routes from most specific to least when using
// a combination of path AND host based routes.  It also ensures that a host based route still allows path based
// matches via the host header.
//
// For example, the http server simulator acts as if it has a directory structure like:
// /var/www
//         index.html (Hello Pod)
//         /test
//              index.html (Hello Pod Path)
//
// With just a path based route for www.example.com/test I should get Hello Pod Path for a curl to www.example.com/test
// A curl to www.example.com should fall through to the default handlers.  In the test environment it will fall through
// to a call to 0.0.0.0:8080 which is the master simulator
//
// If a host based route for www.example.com is added into the mix I should then be able to curl www.example.com and get
// Hello Pod and still be able to curl www.example.com/test and get Hello Pod Path
//
// If the path based route is deleted I should still be able to curl both routes successfully using the host based path
func TestRouterPathSpecificity(t *testing.T) {
	fakeMasterAndPod := tr.NewTestHttpService()
	err := fakeMasterAndPod.Start()
	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}
	defer fakeMasterAndPod.Stop()

	validateServer(fakeMasterAndPod, t)

	dockerCli, err := testutil.NewDockerClient()
	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr, statsPort, 1)
	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}
	defer cleanUp(dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	alternateHttpEndpoint, err := getEndpoint(fakeMasterAndPod.AlternatePodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	waitForRouterToBecomeAvailable("127.0.0.1", statsPort)

	now := unversioned.Now()

	//create path based route
	endpointEvent := &watch.Event{
		Type: watch.Added,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				CreationTimestamp: now,
				Name:              "myService",
				Namespace:         "default",
			},
			Subsets: []kapi.EndpointSubset{httpEndpoint},
		},
	}
	routeEvent := &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "path",
				Namespace: "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				Path: "/test",
				To: kapi.ObjectReference{
					Name: "myService",
				},
			},
		},
	}

	routeAddress := getRouteAddress()
	routeTestAddress := fmt.Sprintf("%s/test", routeAddress)

	fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	//ensure you can curl path but not main host
	if valid, response := isValidRoute(routeTestAddress, "www.example.com", "http", tr.HelloPodPath); !valid {
		t.Errorf("unexpected response: %q", response)
	}

	//create newer, conflicting path based route
	endpointEvent = &watch.Event{
		Type: watch.Added,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "altService",
				Namespace: "alt",
			},
			Subsets: []kapi.EndpointSubset{alternateHttpEndpoint},
		},
	}
	routeEvent = &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				CreationTimestamp: unversioned.Time{Time: now.Add(time.Hour)},
				Name:              "path",
				Namespace:         "alt",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				Path: "/test",
				To: kapi.ObjectReference{
					Name: "altService",
				},
			},
		},
	}
	fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	if valid, response := isValidRoute(routeTestAddress, "www.example.com", "http", tr.HelloPodPath); !valid {
		t.Errorf("unexpected response: %q", response)
	}

	//create host based route
	routeEvent = &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				CreationTimestamp: now,
				Name:              "host",
				Namespace:         "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				To: kapi.ObjectReference{
					Name: "myService",
				},
			},
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	//ensure you can curl path and host
	if valid, response := isValidRoute(routeTestAddress, "www.example.com", "http", tr.HelloPodPath); !valid {
		t.Errorf("unexpected response: %q", response)
	}
	if valid, response := isValidRoute(routeAddress, "www.example.com", "http", tr.HelloPod); !valid {
		t.Errorf("unexpected response: %q", response)
	}

	//delete path based route
	routeEvent = &watch.Event{
		Type: watch.Deleted,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "path",
				Namespace: "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				Path: "/test",
				To: kapi.ObjectReference{
					Name: "myService",
				},
			},
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	// Ensure you can still curl path and host.  The host-based route should now
	// handle requests to / as well as requests to /test (or any other path).
	// Note, however, that the host-based route and the host-based route use the
	// same service, and that that service varies its response in accordance with
	// the path, so we still get the tr.HelloPodPath response when we request
	// /test even though we request using routeAddress.
	if valid, response := isValidRoute(routeTestAddress, "www.example.com", "http", tr.HelloPodPath); !valid {
		t.Errorf("unexpected response: %q", response)
	}
	if valid, response := isValidRoute(routeAddress, "www.example.com", "http", tr.HelloPod); !valid {
		t.Errorf("unexpected response: %q", response)
	}

	// create newer, conflicting host based route that is ignored
	routeEvent = &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				CreationTimestamp: unversioned.Time{Time: now.Add(time.Hour)},
				Name:              "host",
				Namespace:         "alt",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				To: kapi.ObjectReference{
					Name: "altService",
				},
			},
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	if valid, response := isValidRoute(routeTestAddress, "www.example.com", "http", tr.HelloPodPath); !valid {
		t.Errorf("unexpected response: %q", response)
	}
	if valid, response := isValidRoute(routeAddress, "www.example.com", "http", tr.HelloPod); !valid {
		t.Errorf("unexpected response: %q", response)
	}

	//create old, conflicting host based route which should take over the route
	routeEvent = &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				CreationTimestamp: unversioned.Time{Time: now.Add(-time.Hour)},
				Name:              "host",
				Namespace:         "alt",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				To: kapi.ObjectReference{
					Name: "altService",
				},
			},
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	if valid, response := isValidRoute(routeTestAddress, "www.example.com", "http", tr.HelloPodAlternate); !valid {
		t.Errorf("unexpected response: %q", response)
	}
	if valid, response := isValidRoute(routeAddress, "www.example.com", "http", tr.HelloPodAlternate); !valid {
		t.Errorf("unexpected response: %q", response)
	}

	// Clean up the host-based route and endpoint.
	routeEvent = &watch.Event{
		Type: watch.Deleted,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "host",
				Namespace: "default",
			},
			Spec: routeapi.RouteSpec{
				Host: "www.example.com",
				To: kapi.ObjectReference{
					Name: "myService",
				},
			},
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	endpointEvent = &watch.Event{
		Type: watch.Modified,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "myService",
				Namespace: "default",
			},
			Subsets: []kapi.EndpointSubset{},
		},
	}
	fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)

	time.Sleep(time.Second * 5)
}
Exemple #15
0
// TestRouterReloadCoalesce tests that router reloads are coalesced.
func TestRouterReloadCoalesce(t *testing.T) {
	//create a server which will act as a user deployed application that
	//serves http and https as well as act as a master to simulate watches
	fakeMasterAndPod := tr.NewTestHttpService()
	defer fakeMasterAndPod.Stop()

	err := fakeMasterAndPod.Start()
	validateServer(fakeMasterAndPod, t)

	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}

	//deploy router docker container
	dockerCli, err := testutil.NewDockerClient()

	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	reloadInterval := 7

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr, statsPort, reloadInterval)

	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}

	defer cleanUp(dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}
	_, err = getEndpoint(fakeMasterAndPod.PodHttpsAddr)
	if err != nil {
		t.Fatalf("Couldn't get https endpoint: %v", err)
	}
	_, err = getEndpoint(fakeMasterAndPod.AlternatePodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	routeAddress := getRouteAddress()

	//  Wait for the router to come up + reload interval to elapse.
	time.Sleep(time.Second * 10)

	routeAlias := "www.example.test"
	serviceName := "example"
	endpoints := []kapi.EndpointSubset{httpEndpoint}
	numRoutes := 10

	for i := 1; i <= numRoutes; i++ {
		routeName := fmt.Sprintf("coalesce-route-%v", i)
		routeAlias = fmt.Sprintf("www.example-coalesce-%v.test", i)

		// Send the add events.
		generateTestEvents(fakeMasterAndPod, false, serviceName, routeName, routeAlias, endpoints)
		time.Sleep(time.Second * tcWaitSeconds)
	}

	// Wait for the last routeAlias to become available.
	ttl := reloadInterval * 2
	for i := 0; i < ttl; i++ {
		// Wait for router to pick up configs.
		time.Sleep(time.Second * tcWaitSeconds)

		// Now verify the route with an HTTP client.
		resp, err := getRoute(routeAddress, routeAlias, "http", nil, tr.HelloPod)
		if err == nil {
			if resp == tr.HelloPod {
				break
			}
		}

		if i != ttl-1 {
			continue
		}
		t.Errorf("Unable to verify response: %v", err)
	}

	// And ensure all the coalesce route aliases are available.
	for i := 1; i <= numRoutes; i++ {
		routeAlias := fmt.Sprintf("www.example-coalesce-%v.test", i)
		resp, err := getRoute(routeAddress, routeAlias, "http", nil, tr.HelloPod)
		if err != nil {
			t.Errorf("Unable to verify response for %q: %v", routeAlias, err)
		}

		if resp != tr.HelloPod {
			t.Errorf("Route %s failed! Response body %q did not match expected %q", routeAlias, resp, tr.HelloPod)

		}
	}

	for i := 1; i <= numRoutes; i++ {
		routeName := fmt.Sprintf("coalesce-route-%v", i)
		routeAlias = fmt.Sprintf("www.example-coalesce-%v.test", i)

		// Send the cleanup events.
		generateTestEvents(fakeMasterAndPod, true, serviceName, routeName, routeAlias, endpoints)
		time.Sleep(time.Second * tcWaitSeconds)
	}

	// Wait for the first routeAlias to become unavailable.
	routeAlias = "www.example-coalesce-1.test"
	ttl = reloadInterval * 2
	for i := 0; i < ttl; i++ {
		// Wait for router to pick up configs.
		time.Sleep(time.Second * tcWaitSeconds)

		// Now verify the route with an HTTP client.
		resp, err := getRoute(routeAddress, routeAlias, "http", nil, tr.HelloPod)
		if err != nil {
			t.Errorf("Unable to verify response for %q: %v", routeAlias, err)
		}

		if resp == tr.HelloPod {
			if i != ttl-1 {
				continue
			}
		}
	}

	// And ensure all the route aliases are gone.
	for i := 1; i <= numRoutes; i++ {
		routeAlias := fmt.Sprintf("www.example-coalesce-%v.test", i)
		resp, err := getRoute(routeAddress, routeAlias, "http", nil, tr.HelloPod)
		if err != nil {
			t.Errorf("Unable to verify route deletion for %q: %+v", routeAlias, err)
		}

		if resp == tr.HelloPod {
			t.Errorf("Unable to verify route deletion for %q: %+v", routeAlias, resp)
		}

		if !strings.Contains(resp, "503 Service Unavailable") {
			t.Errorf("Unable to verify route deletion for %q: %+v", routeAlias, resp)
		}
	}
}
Exemple #16
0
// TestRouterPathSpecificity tests that the router is matching routes from most specific to least when using
// a combination of path AND host based routes.  It also ensures that a host based route still allows path based
// matches via the host header.
//
// For example, the http server simulator acts as if it has a directory structure like:
// /var/www
//         index.html (Hello Pod)
//         /test
//              index.html (Hello Pod Path)
//
// With just a path based route for www.example.com/test I should get Hello Pod Path for a curl to www.example.com/test
// A curl to www.example.com should fall through to the default handlers.  In the test environment it will fall through
// to a call to 0.0.0.0:8080 which is the master simulator
//
// If a host based route for www.example.com is added into the mix I should then be able to curl www.example.com and get
// Hello Pod and still be able to curl www.example.com/test and get Hello Pod Path
//
// If the path based route is deleted I should still be able to curl both routes successfully using the host based path
func TestRouterPathSpecificity(t *testing.T) {
	fakeMasterAndPod := tr.NewTestHttpService()
	err := fakeMasterAndPod.Start()
	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}
	defer fakeMasterAndPod.Stop()

	validateServer(fakeMasterAndPod, t)

	dockerCli, err := testutil.NewDockerClient()
	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr)
	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}
	defer cleanUp(dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}

	//create path based route
	endpointEvent := &watch.Event{
		Type: watch.Added,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "myService",
				Namespace: "default",
			},
			Subsets: []kapi.EndpointSubset{httpEndpoint},
		},
	}
	routeEvent := &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "path",
				Namespace: "default",
			},
			Host:        "www.example.com",
			Path:        "/test",
			ServiceName: "myService",
		},
	}

	routeAddress := getRouteAddress()
	routerTestAddress := fmt.Sprintf("%s/test", routeAddress)

	fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	//ensure you can curl path but not main host
	validateRoute(routerTestAddress, "www.example.com", "http", tr.HelloPodPath, t)

	//create host based route
	routeEvent = &watch.Event{
		Type: watch.Added,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "host",
				Namespace: "default",
			},
			Host:        "www.example.com",
			ServiceName: "myService",
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	//ensure you can curl path and host
	validateRoute(routerTestAddress, "www.example.com", "http", tr.HelloPodPath, t)
	validateRoute(routeAddress, "www.example.com", "http", tr.HelloPod, t)

	//delete path based route
	routeEvent = &watch.Event{
		Type: watch.Deleted,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "path",
				Namespace: "default",
			},
			Host:        "www.example.com",
			Path:        "/test",
			ServiceName: "myService",
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	time.Sleep(time.Second * tcWaitSeconds)
	// Ensure you can still curl path and host.  The host-based route should now
	// handle requests to / as well as requests to /test (or any other path).
	// Note, however, that the host-based route and the host-based route use the
	// same service, and that that service varies its response in accordance with
	// the path, so we still get the tr.HelloPodPath response when we request
	// /test even though we request using routeAddress.
	validateRoute(routerTestAddress, "www.example.com", "http", tr.HelloPodPath, t)
	validateRoute(routeAddress, "www.example.com", "http", tr.HelloPod, t)

	// Clean up the host-based route and endpoint.
	routeEvent = &watch.Event{
		Type: watch.Deleted,
		Object: &routeapi.Route{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "host",
				Namespace: "default",
			},
			Host:        "www.example.com",
			ServiceName: "myService",
		},
	}
	fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	endpointEvent = &watch.Event{
		Type: watch.Modified,
		Object: &kapi.Endpoints{
			ObjectMeta: kapi.ObjectMeta{
				Name:      "myService",
				Namespace: "default",
			},
			Subsets: []kapi.EndpointSubset{},
		},
	}
	fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)

	time.Sleep(time.Second * 5)
}