diff --git a/README.md b/README.md index 1583e901b..620e96e5c 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ include_app_syslog_tcp * `include_sso`: Flag to include the services tests that integrate with Single Sign On. `include_services` must also be set for tests to run. * `include_tasks`: Flag to include the v3 task tests. `include_v3` must also be set for tests to run. The CC API task_creation feature flag must be enabled for these tests to pass. * `include_tcp_routing`: Flag to include the TCP Routing tests. These tests are equivalent to the [TCP Routing tests](https://github.com/cloudfoundry/routing-acceptance-tests/blob/master/tcp_routing/tcp_routing_test.go) from the Routing Acceptance Tests. +* `tcp_domain`: Domain that will be used for apps with TCP routes * `include_user_provided_services`: Flag to include test for user-provided services. * `include_v3`: Flag to include tests for the v3 API. * `include_zipkin`: Flag to include tests for Zipkin tracing. `include_routing` must also be set for tests to run. CF must be deployed with `router.tracing.enable_zipkin` set for tests to pass. @@ -156,6 +157,8 @@ include_app_syslog_tcp * `timeout_scale`: Used primarily to scale default timeouts for test setup and teardown actions (e.g. creating an org) as opposed to main test actions (e.g. pushing an app). * `isolation_segment_name`: Name of the isolation segment to use for the isolation segments test. * `isolation_segment_domain`: Domain that will route to the isolated router in the isolation segments and routing isolation segments tests. [See below](#routing-isolation-segments) +* `include_tcp_isolation_segments`: Flag to include the TCP Routing tests on Isolation Segments. These tests are equivalent to the [TCP Routing tests](https://github.com/cloudfoundry/routing-acceptance-tests/blob/master/tcp_routing/tcp_routing_test.go) from the Routing Acceptance Tests. +* `isolation_segment_tcp_domain`: Domain that will be used for isolated apps with TCP routes * `private_docker_registry_image`: Name of the private docker image to use when testing private docker registries. [See below](#private-docker) * `private_docker_registry_username`: Username to access the private docker repository. [See below](#private-docker) * `private_docker_registry_password`: Password to access the private docker repository. [See below](#private-docker) diff --git a/cats_suite_helpers/cats_suite_helpers.go b/cats_suite_helpers/cats_suite_helpers.go index 8f90ea2b8..44362631c 100644 --- a/cats_suite_helpers/cats_suite_helpers.go +++ b/cats_suite_helpers/cats_suite_helpers.go @@ -1,6 +1,7 @@ package cats_suite_helpers import ( + "bytes" "fmt" "net" "regexp" @@ -56,7 +57,7 @@ func AppsDescribe(description string, callback func()) bool { func IsolatedTCPRoutingDescribe(description string, callback func()) bool { return Describe("[isolated tcp routing]", func() { BeforeEach(func() { - if Config.GetIncludeRoutingIsolationSegments() || !Config.GetIncludeTCPIsolationSegments() { + if !Config.GetIncludeTCPIsolationSegments() { Skip(skip_messages.SkipIsolatedTCPRoutingMessage) } }) @@ -377,6 +378,17 @@ func WindowsDescribe(description string, callback func()) bool { }) } +func WindowsTCPRoutingDescribe(description string, callback func()) bool { + return Describe("[windows routing]", func() { + BeforeEach(func() { + if !Config.GetIncludeTCPRouting() || !Config.GetIncludeWindows() { + Skip(skip_messages.SkipTCPRoutingMessage) + } + }) + Describe(description, callback) + }) +} + func VolumeServicesDescribe(description string, callback func()) bool { return Describe("[volume_services]", func() { BeforeEach(func() { @@ -451,5 +463,12 @@ func SendAndReceive(addr string, externalPort string) (string, error) { return "", err } - return string(buff), nil + // only grab up to the first null byte of a message since we have a predefined slice length that may not be full + i := len(buff) + + if j := bytes.IndexByte(buff, 0); j > 0 { + i = j + } + + return string(buff[:i]), nil } diff --git a/helpers/config/config.go b/helpers/config/config.go index 2ba40e396..a1069eaa7 100644 --- a/helpers/config/config.go +++ b/helpers/config/config.go @@ -47,6 +47,7 @@ type CatsConfig interface { GetAdminClientSecret() string GetApiEndpoint() string GetAppsDomain() string + GetTCPDomain() string GetArtifactsDirectory() string GetBinaryBuildpackName() string GetStaticFileBuildpackName() string @@ -67,6 +68,7 @@ type CatsConfig interface { GetHwcBuildpackName() string GetIsolationSegmentName() string GetIsolationSegmentDomain() string + GetIsolationSegmentTCPDomain() string GetJavaBuildpackName() string GetNamePrefix() string GetNginxBuildpackName() string diff --git a/helpers/config/config_struct.go b/helpers/config/config_struct.go index a0da459d0..07b199ea6 100644 --- a/helpers/config/config_struct.go +++ b/helpers/config/config_struct.go @@ -19,6 +19,7 @@ const ( type config struct { ApiEndpoint *string `json:"api"` AppsDomain *string `json:"apps_domain"` + TCPDomain *string `json:"tcp_domain"` UseHttp *bool `json:"use_http"` AdminPassword *string `json:"admin_password"` @@ -34,8 +35,9 @@ type config struct { ConfigurableTestPassword *string `json:"test_password"` - IsolationSegmentName *string `json:"isolation_segment_name"` - IsolationSegmentDomain *string `json:"isolation_segment_domain"` + IsolationSegmentName *string `json:"isolation_segment_name"` + IsolationSegmentDomain *string `json:"isolation_segment_domain"` + IsolationSegmentTCPDomain *string `json:"isolation_segment_tcp_domain"` SkipSSLValidation *bool `json:"skip_ssl_validation"` @@ -811,6 +813,14 @@ func (c *config) GetAppsDomain() string { return *c.AppsDomain } +func (c *config) GetTCPDomain() string { + if c.TCPDomain == nil || *c.TCPDomain == "" { + return fmt.Sprintf("tcp.%s", *c.AppsDomain) + } + + return *c.TCPDomain +} + func (c *config) GetSkipSSLValidation() bool { return *c.SkipSSLValidation } @@ -827,6 +837,10 @@ func (c *config) GetIsolationSegmentDomain() string { return *c.IsolationSegmentDomain } +func (c *config) GetIsolationSegmentTCPDomain() string { + return *c.IsolationSegmentTCPDomain +} + func (c *config) GetNamePrefix() string { return *c.NamePrefix } diff --git a/isolation_segments/isolation_segments.go b/isolation_segments/isolation_segments.go index 18088ed77..9f03b5759 100644 --- a/isolation_segments/isolation_segments.go +++ b/isolation_segments/isolation_segments.go @@ -241,7 +241,7 @@ var _ = IsolationSegmentsDescribe("IsolationSegments", func() { var domainName string BeforeEach(func() { - domainName = fmt.Sprintf("tcp.%s", isoSegDomain) + domainName = Config.GetIsolationSegmentTCPDomain() workflowhelpers.AsUser(TestSetup.AdminUserContext(), Config.DefaultTimeoutDuration(), func() { v3_helpers.EntitleOrgToIsolationSegment(orgGuid, isoSegGuid) session := cf.Cf("curl", fmt.Sprintf("/v3/spaces?names=%s", spaceName)) @@ -346,10 +346,13 @@ var _ = IsolationSegmentsDescribe("IsolationSegments", func() { }) It("maps single external port to both applications", func() { - serverResponses, err := GetNServerResponses(10, domainName, externalPort1) - Expect(err).ToNot(HaveOccurred()) - Expect(serverResponses).To(ContainElement(ContainSubstring(serverId1))) - Expect(serverResponses).To(ContainElement(ContainSubstring(serverId2))) + getServerResponses := func() []string { + serverResponses, err := GetNServerResponses(10, domainName, externalPort1) + Expect(err).ToNot(HaveOccurred()) + return serverResponses + } + Eventually(getServerResponses, 30).Should(ContainElement(ContainSubstring(serverId1))) + Eventually(getServerResponses, 30).Should(ContainElement(ContainSubstring(serverId2))) }) }) diff --git a/tcp_routing/tcp_routing.go b/tcp_routing/tcp_routing.go index 54e91ef71..70d0d5e54 100644 --- a/tcp_routing/tcp_routing.go +++ b/tcp_routing/tcp_routing.go @@ -21,7 +21,7 @@ var _ = TCPRoutingDescribe("TCP Routing", func() { var domainName string BeforeEach(func() { - domainName = fmt.Sprintf("tcp.%s", Config.GetAppsDomain()) + domainName = Config.GetTCPDomain() workflowhelpers.AsUser(TestSetup.AdminUserContext(), Config.DefaultTimeoutDuration(), func() { routerGroupOutput := string(cf.Cf("router-groups").Wait().Out.Contents()) Expect(routerGroupOutput).To( diff --git a/windows/tcp_routing.go b/windows/tcp_routing.go new file mode 100644 index 000000000..8107f97a0 --- /dev/null +++ b/windows/tcp_routing.go @@ -0,0 +1,150 @@ +package windows + +import ( + "fmt" + "os" + "path/filepath" + + . "github.com/cloudfoundry/cf-acceptance-tests/cats_suite_helpers" + "github.com/cloudfoundry/cf-acceptance-tests/helpers/app_helpers" + "github.com/cloudfoundry/cf-acceptance-tests/helpers/assets" + "github.com/cloudfoundry/cf-acceptance-tests/helpers/random_name" + "github.com/cloudfoundry/cf-test-helpers/v2/cf" + "github.com/cloudfoundry/cf-test-helpers/v2/workflowhelpers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" + . "github.com/onsi/gomega/gexec" +) + +const DefaultRouterGroupName = "default-tcp" + +var _ = WindowsTCPRoutingDescribe("Windows TCP Routing", func() { + var domainName string + var compiledApp string + + BeforeEach(func() { + domainName = Config.GetTCPDomain() + workflowhelpers.AsUser(TestSetup.AdminUserContext(), Config.DefaultTimeoutDuration(), func() { + routerGroupOutput := string(cf.Cf("router-groups").Wait().Out.Contents()) + Expect(routerGroupOutput).To( + MatchRegexp(fmt.Sprintf("%s\\s+tcp", DefaultRouterGroupName)), + fmt.Sprintf("Router group %s of type tcp doesn't exist", DefaultRouterGroupName), + ) + + Expect(cf.Cf("create-shared-domain", + domainName, + "--router-group", DefaultRouterGroupName, + ).Wait()).To(Exit()) + }) + + originalDir, err := os.Getwd() + Expect(err).NotTo(HaveOccurred()) + err = os.Chdir(assets.NewAssets().TCPListener) + Expect(err).NotTo(HaveOccurred()) + compiledApp, err = gexec.BuildWithEnvironment(".", []string{"GOOS=windows"}) + Expect(err).NotTo(HaveOccurred()) + err = os.Chdir(originalDir) + Expect(err).NotTo(HaveOccurred()) + }) + + Context("external ports", func() { + var ( + appName string + tcpDropletReceiver = assets.NewAssets().TCPListener + serverId1 = "server1" + externalPort1 string + ) + + BeforeEach(func() { + appName = random_name.CATSRandomName("APP") + cmd := fmt.Sprintf("tcp-listener --serverId=%s", serverId1) + + Expect(cf.Cf("push", + "--no-route", + "--no-start", + appName, + "-p", compiledApp, + "-b", Config.GetBinaryBuildpackName(), + "-s", "windows", + "-m", DEFAULT_MEMORY_LIMIT, + "-f", filepath.Join(tcpDropletReceiver, "manifest.yml"), + "-c", cmd, + ).Wait()).To(Exit(0)) + externalPort1 = MapTCPRoute(appName, domainName) + Expect(cf.Cf("start", appName).Wait(Config.CfPushTimeoutDuration())).To(Exit(0)) + }) + + AfterEach(func() { + app_helpers.AppReport(appName) + Eventually(cf.Cf("delete", appName, "-f", "-r")).Should(Exit(0)) + }) + + It("maps a single external port to an application's container port", func() { + resp, err := SendAndReceive(domainName, externalPort1) + Expect(err).ToNot(HaveOccurred()) + Expect(resp).To(ContainSubstring(serverId1)) + }) + + Context("with two different apps", func() { + var ( + secondAppName string + serverId2 = "server2" + ) + + BeforeEach(func() { + secondAppName = random_name.CATSRandomName("APP") + cmd := fmt.Sprintf("tcp-listener --serverId=%s", serverId2) + + Expect(cf.Cf("push", + "--no-route", + "--no-start", + secondAppName, + "-p", compiledApp, + "-s", "windows", + "-b", Config.GetBinaryBuildpackName(), + "-m", DEFAULT_MEMORY_LIMIT, + "-f", filepath.Join(tcpDropletReceiver, "manifest.yml"), + "-c", cmd, + ).Wait()).To(Exit(0)) + + Expect(cf.Cf("map-route", + secondAppName, domainName, "--port", externalPort1, + ).Wait()).To(Exit(0)) + Expect(cf.Cf("start", secondAppName).Wait(Config.CfPushTimeoutDuration())).To(Exit(0)) + }) + + AfterEach(func() { + app_helpers.AppReport(secondAppName) + Eventually(cf.Cf("delete-route", domainName, "--port", externalPort1, "-f")).Should(Exit(0)) + Eventually(cf.Cf("delete", appName, "-f", "-r")).Should(Exit(0)) + Eventually(cf.Cf("delete", secondAppName, "-f", "-r")).Should(Exit(0)) + }) + + It("maps single external port to both applications", func() { + serverResponses, err := GetNServerResponses(10, domainName, externalPort1) + Expect(err).ToNot(HaveOccurred()) + Expect(serverResponses).To(ContainElement(ContainSubstring(serverId1))) + Expect(serverResponses).To(ContainElement(ContainSubstring(serverId2))) + }) + }) + + Context("with a second external port", func() { + var externalPort2 string + + BeforeEach(func() { + externalPort2 = MapTCPRoute(appName, domainName) + }) + + It("maps both ports to the same application", func() { + resp1, err := SendAndReceive(domainName, externalPort1) + Expect(err).ToNot(HaveOccurred()) + Expect(resp1).To(ContainSubstring(serverId1)) + + resp2, err := SendAndReceive(domainName, externalPort2) + Expect(err).ToNot(HaveOccurred()) + Expect(resp2).To(ContainSubstring(serverId1)) + }) + }) + }) +})