Repository: kumahq/kuma
Pull request #1445 (2021-01-19 14:39:39)

chore(deps): bump github.com/gogo/protobuf from 1.3.1 to 1.3.2
Bumps [github.com/gogo/protobuf](https://github.com/gogo/protobuf) from 1.3.1 to 1.3.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/gogo/protobuf/releases">github.com/gogo/protobuf's releases</a>.</em></p>
<blockquote>
<h2>Release v.1.3.2</h2>
<h2>Tested versions:</h2>
<p>go 1.15.6
protoc 3.14.0</p>
<h2>Bug fixes:</h2>
<p>skippy peanut butter</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc"><code>b03c65e</code></a> skippy peanut butter</li>
<li><a href="https://github.com/gogo/protobuf/commit/550e88954e617545f49920b752c154d72abf1d8d"><code>550e889</code></a> update to go version 1.15.6 and protoc 3.14.0 (<a href="https://github-redirect.dependabot.com/gogo/protobuf/issues/717">#717</a>)</li>
<li><a href="https://github.com/gogo/protobuf/commit/deb6fe8ca7c6d06584bfbd40ca407bf69d9fd2aa"><code>deb6fe8</code></a> Update Readme.md</li>
<li>See full diff in <a href="https://github.com/gogo/protobuf/compare/v1.3.1...v1.3.2">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/gogo/protobuf&package-manager=go_modules&previous-version=1.3.1&new-version=1.3.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/kumahq/kuma?pullRequest=1445) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/kumahq/kuma?pullRequest=1445) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/kumahq/kuma?pullRequest=1445) it.</sub>
dependabot[bot]: Dependabot tried to update this pull request, but something went wrong. We're looking into it, but in the meantime you can retry the update by commenting `@dependabot rebase`.
nickolaev: @mergifyio update
mergify[bot]: **Command `update`: success**
> **Branch has been successfully updated**
> 


Patch:
diff --git a/docs/proposals/dns-naming-compliance.md b/docs/proposals/dns-naming-compliance.md
new file mode 100644
index 000000000..896f44345
--- /dev/null
+++ b/docs/proposals/dns-naming-compliance.md
@@ -0,0 +1,90 @@
+# DNS compliant names in Kuma
+
+## Current state
+
+### Service Naming
+
+In a broader sense, Kuma uses the `kuma.io/service` tag to mark a certain combination of ip/address and port as a particular service, which then
+can be used in the Policies selectors.
+
+On Kubernetes, this tag is generated automatically and is an underscore delimited combination of the K8s Service Name, Namespace, and Port: `<name>_<namespace>_svc_<port>`.
+
+On Universal, one still has to generate the name in an arbitrary form.
+
+### Cross-zone communication
+
+To account for multizone and hybrid deployments, we have extended the naming scheme through a DNS resolving service by adding a `.mesh` suffix.
+This unifies the naming of services in a way that it does not matter where the service was instantiated. Of course the naming scheme in Kubernetes
+is still applied, but now we can also also call `<name>_<namespace>_svc_<port>.mesh`.
+
+By adding the transparent proxy capabilities to Universal, we can now refer to services in the `.mesh` domain too.
+
+The DNS server in the kuma-cp will generate and assign virtual IPs (VIPs) so that any service name resolves to it, and that
+the side-cars get an additional listener for the service cluster. The default VIPs CIDR is `240.0.0.1`.
+
+### Exposing services outside the mesh
+
+The typical way to expose services from within the mesh to the external consumers is through a gateway dataplane proxy (one the Kuma side) and
+a reverse proxy on the customer-facing side. In this architecture, the traffic routes on the proxy should refer to Kuma services so that the
+listeners on the side-car Envoy, are hit with the traffic and then load-balanced through the available local and remote endpoints.
+
+On Kubernetes this happens using an Ingress resource of the form:
+
+```yaml
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: marketplace
+  namespace: default
+  annotations:
+    kubernetes.io/ingress.class: kong
+    kubernetes.io/ingress.global-static-ip-name: "kong-demo-ip"
+spec:
+  rules:
+  - host: localhost
+    http:
+      paths:
+      - path: /
+        backend:
+          serviceName: frontend
+          servicePort: 80
+```
+
+Where `serviceName: frontend` is the name of a Kubernetes Service object, which will be used by the proxy to redirect the incoming requests.
+This same service in Kuma namespace will be called `frontend_default_svc_80` and can also be resolved as a DNS name `frontend_default_svc_80.mesh`.
+
+However, the problem is that the Kubernetes Ingress Resource enforces 2 rules here:
+* `serviceName` can refer only to a Kubernetes Service
+* this service can not contain `_` or `.`
+
+### Illustrating the problem
+To illustrate the problem further, let's look at the option to manually create a Service of type `ExternalName`
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: frontend
+  namespace: default
+  annotations:
+    ingress.kubernetes.io/service-upstream: "true"
+spec:
+  type: ExternalName
+  externalName: frontend_default_svc_80.mesh
+```
+
+```shell
+The Service "frontend" is invalid: spec.externalName: Invalid value: "frontend_default_svc_80.mesh": a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')
+```
+
+The DNS name we try to supply is invalid according to the relevant RFCs and the Kubernetes embedded checks. So we are not even able to apply that resource.
+
+## Proposal for improvements
+
+In addition to the already existing Kubernetes generated `frontend_default_svc_80.mesh`, we can add a couple of compliant aliases:
+* use dashes as separators `frontend-default-svc-80.mesh`. This can get compllicated, cosider a service name `my-complex-service`
+  which naturally translates to `my-complex-service_default_svc_80.mesh`, and then the all dashes version `my-complex-service-default-svc-80.mesh`
+* use dots as separators `frontend.default.svc.80.mesh`. The complex service version then will be slighly more readable `my-complex-service.default.svc.80.mesh`
+
+All three of these should refer to the same VIP. We can have a `kumactl` command that enumerates the services and ServiceInsights and expose
+the DNS names associated with it.
diff --git a/go.mod b/go.mod
index 56fb62ff0..d714f54c5 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@ require (
 	github.com/go-kit/kit v0.10.0
 	github.com/go-logr/logr v0.1.0
 	github.com/go-logr/zapr v0.1.1
-	github.com/gogo/protobuf v1.3.1
+	github.com/gogo/protobuf v1.3.2
 	github.com/golang-migrate/migrate/v4 v4.14.1
 	github.com/golang/protobuf v1.4.3
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
diff --git a/go.sum b/go.sum
index e8d425078..15c2117b5 100644
--- a/go.sum
+++ b/go.sum
@@ -485,6 +485,8 @@ github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48/go.mod h1:SlYgWuQ5
 github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang-migrate/migrate/v4 v4.14.1 h1:qmRd/rNGjM1r3Ve5gHd5ZplytrD02UcItYNxJ3iUHHE=
 github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
@@ -738,6 +740,7 @@ github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dv
 github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
@@ -1495,6 +1498,7 @@ golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roY
 golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
@@ -1502,6 +1506,7 @@ golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210111221946-d33bae441459 h1:/pJfBBZw1zeMQA6vRpalDcuw4VJ/B6+N5obtmG0Xiv8=
 golang.org/x/tools v0.0.0-20210111221946-d33bae441459/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/pkg/dns/components.go b/pkg/dns/components.go
index e489f0738..6c679a88e 100644
--- a/pkg/dns/components.go
+++ b/pkg/dns/components.go
@@ -19,6 +19,7 @@ func Setup(rt runtime.Runtime) error {
 		rt.Config().DNSServer.Port,
 		rt.DNSResolver(),
 		rt.Metrics(),
+		DnsNameToKumaCompliant,
 	)
 	if err != nil {
 		return err
diff --git a/pkg/dns/name_modifier.go b/pkg/dns/name_modifier.go
new file mode 100644
index 000000000..67ae4b7a7
--- /dev/null
+++ b/pkg/dns/name_modifier.go
@@ -0,0 +1,26 @@
+package dns
+
+import "strings"
+
+func DnsNameToKumaCompliant(name string) (string, error) {
+	// the request might be of the form:
+	//  service-name.namespace.something-else.mesh.
+	// it will always end with a dot, as specified by https://tools.ietf.org/html/rfc1034#section-3.1
+	//  `Since a complete domain name ends with the root label, this leads to a printed form which ends in a dot.`
+	countDots := strings.Count(name, ".")
+	toReplace := countDots
+	switch countDots {
+	case 0:
+		return name, nil
+	case 1:
+		if name[len(name)-1] == '.' {
+			return name, nil
+		}
+	default:
+		if name[len(name)-1] == '.' {
+			toReplace--
+		}
+	}
+
+	return strings.Replace(name, ".", "_", toReplace-1), nil
+}
diff --git a/pkg/dns/server.go b/pkg/dns/server.go
index 26c9b76a9..186e9b9f9 100644
--- a/pkg/dns/server.go
+++ b/pkg/dns/server.go
@@ -21,15 +21,18 @@ type DNSServer interface {
 	NeedLeaderElection() bool
 }
 
+type NameModifier = func(qName string) (string, error)
+
 type SimpleDNSServer struct {
 	address  string
 	resolver resolver.DNSResolver
 
 	latencyMetric    prometheus.Summary
 	resolutionMetric *prometheus.CounterVec
+	nameModifier     NameModifier
 }
 
-func NewDNSServer(port uint32, resolver resolver.DNSResolver, metrics core_metrics.Metrics) (DNSServer, error) {
+func NewDNSServer(port uint32, resolver resolver.DNSResolver, metrics core_metrics.Metrics, modifier NameModifier) (DNSServer, error) {
 	handler := &SimpleDNSServer{
 		address:  fmt.Sprintf("0.0.0.0:%d", port),
 		resolver: resolver,
@@ -42,6 +45,7 @@ func NewDNSServer(port uint32, resolver resolver.DNSResolver, metrics core_metri
 			Name: "dns_server_resolution",
 			Help: "Counter for DNS Server resolutions",
 		}, []string{"result"}),
+		nameModifier: modifier,
 	}
 	if err := metrics.Register(handler.latencyMetric); err != nil {
 		return nil, err
@@ -57,8 +61,8 @@ func (h *SimpleDNSServer) parseQuery(m *dns.Msg) {
 	for _, q := range m.Question {
 		switch q.Qtype {
 		case dns.TypeA:
-			serverLog.V(1).Info("query for " + q.Name)
-			ip, err := h.resolver.ForwardLookupFQDN(q.Name)
+			serverLog.V(1).Info("received a query for " + q.Name)
+			ip, err := h.lookup(q.Name)
 			if err != nil {
 				serverLog.V(1).Info("unable to resolve", "Name", q.Name, "error", err.Error())
 				h.resolutionMetric.WithLabelValues("unresolved").Inc()
@@ -132,3 +136,24 @@ func (h *SimpleDNSServer) registerDNSHandler() {
 		h.handleDNSRequest(writer, msg)
 	})
 }
+
+func (h *SimpleDNSServer) lookup(qName string) (string, error) {
+	ip, err := h.resolver.ForwardLookupFQDN(qName)
+	if err != nil {
+		if h.nameModifier == nil {
+			return "", err
+		}
+
+		modifiedName, err := h.nameModifier(qName)
+		if err != nil {
+			return "", err
+		}
+
+		ip, err = h.resolver.ForwardLookupFQDN(modifiedName)
+		if err != nil {
+			return "", err
+		}
+	}
+
+	return ip, nil
+}


Test patch:
diff --git a/pkg/dns/name_modifier_test.go b/pkg/dns/name_modifier_test.go
new file mode 100644
index 000000000..b0cc65989
--- /dev/null
+++ b/pkg/dns/name_modifier_test.go
@@ -0,0 +1,57 @@
+package dns_test
+
+import (
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/ginkgo/extensions/table"
+	. "github.com/onsi/gomega"
+
+	"github.com/kumahq/kuma/pkg/dns"
+)
+
+var _ = Describe("DNS name modifier", func() {
+	type testCase struct {
+		input    string
+		expected string
+	}
+
+	DescribeTable("should modify names",
+		func(given testCase) {
+			// when
+			actual, err := dns.DnsNameToKumaCompliant(given.input)
+
+			// then
+			Expect(err).ToNot(HaveOccurred())
+			// and
+			Expect(actual).To(MatchYAML(given.expected))
+		},
+		Entry("empty", testCase{
+			input:    "",
+			expected: "",
+		}),
+		Entry("one last dot", testCase{
+			input:    "mesh.",
+			expected: "mesh.",
+		}),
+		Entry("one dot", testCase{
+			input:    ".mesh",
+			expected: ".mesh",
+		}),
+		Entry("two dots with last", testCase{
+			input:    "a.mesh.",
+			expected: "a.mesh.",
+		}),
+		Entry("two dots", testCase{
+			input:    "a.b.mesh",
+			expected: "a_b.mesh",
+		}),
+		Entry("three dots with last", testCase{
+			input:    "a.b.mesh.",
+			expected: "a_b.mesh.",
+		}),
+		Entry("three dots", testCase{
+			input:    "a.b.c.mesh",
+			expected: "a_b_c.mesh",
+		}),
+	)
+
+})
diff --git a/pkg/dns/server_test.go b/pkg/dns/server_test.go
index b3ecc9c4c..f9e3e3a2c 100644
--- a/pkg/dns/server_test.go
+++ b/pkg/dns/server_test.go
@@ -37,7 +37,7 @@ var _ = Describe("DNS server", func() {
 			m, err := core_metrics.NewMetrics("Standalone")
 			metrics = m
 			Expect(err).ToNot(HaveOccurred())
-			server, err := NewDNSServer(port, dnsResolver, metrics)
+			server, err := NewDNSServer(port, dnsResolver, metrics, DnsNameToKumaCompliant)
 			Expect(err).ToNot(HaveOccurred())
 
 			// given
@@ -190,5 +190,32 @@ var _ = Describe("DNS server", func() {
 			Expect(test_metrics.FindMetric(metrics, "dns_server")).ToNot(BeNil())
 			Expect(test_metrics.FindMetric(metrics, "dns_server_resolution", "result", "resolved").Counter.GetValue()).To(Equal(1.0))
 		})
+
+		It("should resolve converted services with '.'", func() {
+			// given
+			var err error
+			dnsResolver.SetVIPs(vips.List{
+				"my-service_test-namespace_svc_80": "240.0.0.1",
+			})
+			ip, err = dnsResolver.ForwardLookupFQDN("my-service_test-namespace_svc_80.mesh")
+			Expect(err).ToNot(HaveOccurred())
+
+			// when
+			client := new(dns.Client)
+			message := new(dns.Msg)
+			_ = message.SetQuestion("my-service.test-namespace.svc.80.mesh.", dns.TypeA)
+			var response *dns.Msg
+			Eventually(func() error {
+				response, _, err = client.Exchange(message, fmt.Sprintf("127.0.0.1:%d", port))
+				return err
+			}).ShouldNot(HaveOccurred())
+
+			// then
+			Expect(response.Answer[0].String()).To(Equal(fmt.Sprintf("my-service.test-namespace.svc.80.mesh.\t60\tIN\tA\t%s", ip)))
+
+			// and metrics are published
+			Expect(test_metrics.FindMetric(metrics, "dns_server")).ToNot(BeNil())
+			Expect(test_metrics.FindMetric(metrics, "dns_server_resolution", "result", "resolved").Counter.GetValue()).To(Equal(1.0))
+		})
 	})
 })
diff --git a/test/e2e/kuma_deploy_universal_transparent_proxy_test.go b/test/e2e/kuma_deploy_universal_transparent_proxy_test.go
index c7e8c13f6..4c072444c 100644
--- a/test/e2e/kuma_deploy_universal_transparent_proxy_test.go
+++ b/test/e2e/kuma_deploy_universal_transparent_proxy_test.go
@@ -14,7 +14,7 @@ import (
 
 var _ = Describe("Test Universal Transparent Proxy deployment", func() {
 
-	const iterations = 100
+	const iterations = 10
 
 	var cluster Cluster
 	var deployOptsFuncs []DeployOptionsFunc
@@ -65,6 +65,19 @@ var _ = Describe("Test Universal Transparent Proxy deployment", func() {
 					}
 					return "should retry", errors.Errorf("should retry")
 				})
+			retry.DoWithRetry(cluster.GetTesting(), "curl remote service with dots",
+				DefaultRetries, DefaultTimeout,
+				func() (string, error) {
+					stdout, _, err := cluster.ExecWithRetries("", "", "demo-client",
+						"curl", "-v", "-m", "3", "echo-server.kuma-test.svc.8080.mesh")
+					if err != nil {
+						return "should retry", err
+					}
+					if strings.Contains(stdout, "HTTP/1.1 200 OK") {
+						return "Accessing service successful", nil
+					}
+					return "should retry", errors.Errorf("should retry")
+				})
 		}
 	})
 })