// 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) } }
// 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", 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.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", 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: "0.0.0.0/test", }, { name: "passthrough termination", serviceName: "example-passthrough", endpoints: []kapi.EndpointSubset{httpsEndpoint}, routeAlias: "www.example2.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", }, } for _, tc := range testCases { //simulate the events endpointEvent := &watch.Event{ Type: tc.endpointEventType, Object: &kapi.Endpoints{ ObjectMeta: kapi.ObjectMeta{ Name: tc.serviceName, }, Subsets: tc.endpoints, }, } routeEvent := &watch.Event{ Type: tc.routeEventType, Object: &routeapi.Route{ 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) } }
// 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) }