Skip to content

Commit 5c40c5b

Browse files
committed
Add support to override scheme when using DNS service discovery with fallback values
1 parent ba715da commit 5c40c5b

File tree

10 files changed

+162
-50
lines changed

10 files changed

+162
-50
lines changed

pkg/discovery/dnssd/discovery_test.go

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,12 @@ func SubTestWithFallback(_ *TestDiscoveryDI) test.GomegaSubTestFunc {
219219
opt.FallbackHostMappings = []dnssd.HostMapping{
220220
{
221221
ServiceRegex: regexp.MustCompilePOSIX(ServiceName2),
222-
Hosts: []string{"inst-1.{{.ServiceName}}.test.mock:9999", "inst-2.{{.ServiceName}}.test.mock:8888"},
222+
Hosts: []string{
223+
"inst-1.{{.ServiceName}}.test.mock:9999",
224+
"inst-2.{{.ServiceName}}.test.mock",
225+
"http://inst-3.{{.ServiceName}}.test.mock:9999",
226+
"https://inst-4.{{.ServiceName}}.test.mock",
227+
},
223228
},
224229
{
225230
ServiceRegex: regexp.MustCompilePOSIX(`.+`),
@@ -236,23 +241,41 @@ func SubTestWithFallback(_ *TestDiscoveryDI) test.GomegaSubTestFunc {
236241
// via service
237242
svc := instancer.Service()
238243
g.Expect(svc).ToNot(BeNil(), "instancer should return non-nil service")
239-
g.Expect(svc.Insts).To(HaveLen(2), "instancer should return services with fallback instances")
244+
g.Expect(svc.Insts).To(HaveLen(4), "instancer should return services with fallback instances")
240245

241246
// without additional selector
242247
TryInstancerWithMatcher(g, instancer, nil, []*MockedService{
243248
{
244-
AlternativeID: "inst-1." + ServiceName2 + ".test.mock:9999",
249+
AlternativeID: "inst-1." + ServiceName2 + ".test.mock:9999",
245250
AlternativeAddr: "inst-1." + ServiceName2 + ".test.mock",
246-
Name: ServiceName2,
247-
Port: 9999,
248-
Healthy: true,
251+
Name: ServiceName2,
252+
Port: 9999,
253+
Healthy: true,
249254
},
250255
{
251-
AlternativeID: "inst-2." + ServiceName2 + ".test.mock:8888",
256+
AlternativeID: "inst-2." + ServiceName2 + ".test.mock",
252257
AlternativeAddr: "inst-2." + ServiceName2 + ".test.mock",
253-
Name: ServiceName2,
254-
Port: 8888,
255-
Healthy: true,
258+
Name: ServiceName2,
259+
Port: 0,
260+
Healthy: true,
261+
},
262+
{
263+
AlternativeID: "http://inst-3." + ServiceName2 + ".test.mock:9999",
264+
AlternativeAddr: "inst-3." + ServiceName2 + ".test.mock",
265+
Name: ServiceName2,
266+
Port: 9999,
267+
Healthy: true,
268+
AlternativeTags: []string{"insecure=true", "secure=false"},
269+
AlternativeMeta: map[string]string{"scheme": "http"},
270+
},
271+
{
272+
AlternativeID: "https://inst-4." + ServiceName2 + ".test.mock",
273+
AlternativeAddr: "inst-4." + ServiceName2 + ".test.mock",
274+
Name: ServiceName2,
275+
Port: 0,
276+
Healthy: true,
277+
AlternativeTags: []string{"insecure=false", "secure=true"},
278+
AlternativeMeta: map[string]string{"scheme": "https"},
256279
},
257280
})
258281
//with additional selector
@@ -271,11 +294,11 @@ func SubTestWithFallback(_ *TestDiscoveryDI) test.GomegaSubTestFunc {
271294
// without additional selector
272295
TryInstancerWithMatcher(g, instancer, nil, []*MockedService{
273296
{
274-
AlternativeID: "unknown-service.test.mock:0",
297+
AlternativeID: "unknown-service.test.mock",
275298
AlternativeAddr: "unknown-service.test.mock",
276-
Name: "unknown-service",
277-
Port: 0,
278-
Healthy: true,
299+
Name: "unknown-service",
300+
Port: 0,
301+
Healthy: true,
279302
},
280303
})
281304
//with additional selector
@@ -332,7 +355,9 @@ type MockedService struct {
332355
Name string
333356
Port int
334357
Tags []string
358+
AlternativeTags []string // only used for assertion
335359
Meta map[string]string
360+
AlternativeMeta map[string]string // only used for assertion
336361
Healthy bool
337362
}
338363

@@ -372,6 +397,12 @@ func TryInstancerWithMatcher(g *gomega.WithT, instancer discovery.Instancer, mat
372397
g.Expect(inst.Service).To(Equal(svc.Name), "instance with ID [%s] should have correct %s", expectedID, "Service")
373398
g.Expect(inst.Address).To(Equal(expectedAddr), "instance with ID [%s] should have correct %s", expectedID, "Address")
374399
g.Expect(inst.Port).To(Equal(svc.Port), "instance with ID [%s] should have correct %s", expectedID, "Port")
400+
if len(svc.AlternativeTags) > 0 {
401+
g.Expect(inst.Tags).To(Equal(svc.AlternativeTags), "instance with ID [%s] should have correct %s", expectedID, "Tags")
402+
}
403+
if len(svc.AlternativeMeta) > 0 {
404+
g.Expect(inst.Meta).To(Equal(svc.AlternativeMeta), "instance with ID [%s] should have correct %s", expectedID, "Meta")
405+
}
375406
found = true
376407
}
377408
g.Expect(found).To(BeTrue(), "instance with ID [%s] should exists", expectedID)

pkg/discovery/dnssd/instancer.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/cisco-open/go-lanai/pkg/discovery/sd"
1010
"github.com/cisco-open/go-lanai/pkg/utils/loop"
1111
"net"
12+
"regexp"
1213
"sort"
1314
"strconv"
1415
"strings"
@@ -20,11 +21,15 @@ const (
2021
kMetaSRVName = "_srv_name"
2122
kMetaSRVService = "_srv_service"
2223
kMetaSRVProto = "_srv_proto"
24+
kMetaScheme = "scheme"
25+
kTagSecure = "secure"
26+
kTagInsecure = "insecure"
2327
)
2428

2529
var (
2630
defaultRefreshInterval = 30 * time.Second
2731
defaultLookupTimeout = 2 * time.Second
32+
hostPatternRegexp = regexp.MustCompile(`((?P<scheme>\w+)://)?(?P<host>.+)`)
2833
)
2934

3035
type InstancerOptions func(opt *InstancerOption)
@@ -229,19 +234,27 @@ func staticInstancesWithTemplates(opt *InstancerOption) ([]*discovery.Instance,
229234
if e != nil {
230235
return nil, e
231236
}
232-
addr, port, e := splitAddrAndPort(host)
237+
scheme, addr, port, e := parseHostStringWithScheme(host)
233238
if e != nil {
234239
return nil, fmt.Errorf(`unable to parse host "%s": %v`, host, e)
235240
}
236241
instances[j] = &discovery.Instance{
237-
ID: net.JoinHostPort(addr, strconv.Itoa(port)),
242+
ID: host,
238243
Service: opt.Name,
239244
Address: addr,
240245
Port: port,
241246
Meta: map[string]string{},
242247
Health: discovery.HealthPassing,
243248
RawEntry: host,
244249
}
250+
if len(scheme) != 0 {
251+
instances[j].Meta[kMetaScheme] = scheme
252+
if scheme == "http" {
253+
instances[j].Tags = append(instances[j].Tags, kTagInsecure+"=true", kTagSecure+"=false")
254+
} else {
255+
instances[j].Tags = append(instances[j].Tags, kTagInsecure+"=false", kTagSecure+"=true")
256+
}
257+
}
245258
}
246259
sort.SliceStable(instances, func(i, j int) bool {
247260
return instances[i].ID < instances[j].ID
@@ -265,3 +278,18 @@ func splitAddrAndPort(value string) (string, int, error) {
265278
return addr, port, nil
266279
}
267280
}
281+
282+
func parseHostStringWithScheme(value string) (scheme, hostname string, port int, err error) {
283+
var host string
284+
match := hostPatternRegexp.FindStringSubmatch(value)
285+
for i, name := range hostPatternRegexp.SubexpNames() {
286+
switch name {
287+
case "scheme":
288+
scheme = strings.ToLower(match[i])
289+
case "host":
290+
host = strings.ToLower(match[i])
291+
}
292+
}
293+
hostname, port, err = splitAddrAndPort(host)
294+
return
295+
}

pkg/discovery/dnssd/package_test.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -93,23 +93,41 @@ func SubTestDiscoveryClientWithFallback(di *TestModuleDI) test.GomegaSubTestFunc
9393
// via service
9494
svc := instancer.Service()
9595
g.Expect(svc).ToNot(BeNil(), "instancer should return non-nil service")
96-
g.Expect(svc.Insts).To(HaveLen(2), "instancer should return services with all matching instances")
96+
g.Expect(svc.Insts).To(HaveLen(4), "instancer should return services with all matching instances")
9797

9898
// without additional selector
9999
TryInstancerWithMatcher(g, instancer, nil, []*MockedService{
100100
{
101-
AlternativeID: "inst-1." + ServiceName2 + ".test.mock:9999",
101+
AlternativeID: "inst-1." + ServiceName2 + ".test.mock:9999",
102102
AlternativeAddr: "inst-1." + ServiceName2 + ".test.mock",
103-
Name: ServiceName2,
104-
Port: 9999,
105-
Healthy: true,
103+
Name: ServiceName2,
104+
Port: 9999,
105+
Healthy: true,
106106
},
107107
{
108-
AlternativeID: "inst-2." + ServiceName2 + ".test.mock:8888",
108+
AlternativeID: "inst-2." + ServiceName2 + ".test.mock",
109109
AlternativeAddr: "inst-2." + ServiceName2 + ".test.mock",
110-
Name: ServiceName2,
111-
Port: 8888,
112-
Healthy: true,
110+
Name: ServiceName2,
111+
Port: 0,
112+
Healthy: true,
113+
},
114+
{
115+
AlternativeID: "http://inst-3." + ServiceName2 + ".test.mock:9999",
116+
AlternativeAddr: "inst-3." + ServiceName2 + ".test.mock",
117+
Name: ServiceName2,
118+
Port: 9999,
119+
Healthy: true,
120+
AlternativeTags: []string{"insecure=true", "secure=false"},
121+
AlternativeMeta: map[string]string{"scheme": "http"},
122+
},
123+
{
124+
AlternativeID: "https://inst-4." + ServiceName2 + ".test.mock",
125+
AlternativeAddr: "inst-4." + ServiceName2 + ".test.mock",
126+
Name: ServiceName2,
127+
Port: 0,
128+
Healthy: true,
129+
AlternativeTags: []string{"insecure=false", "secure=true"},
130+
AlternativeMeta: map[string]string{"scheme": "https"},
113131
},
114132
})
115133
}
@@ -134,11 +152,11 @@ func SubTestDiscoveryClientWithUnknownService(di *TestModuleDI) test.GomegaSubTe
134152
// without additional selector
135153
TryInstancerWithMatcher(g, instancer, nil, []*MockedService{
136154
{
137-
AlternativeID: svcName + ".test.mock:0",
155+
AlternativeID: svcName + ".test.mock",
138156
AlternativeAddr: svcName + ".test.mock",
139-
Name: svcName,
140-
Port: 0,
141-
Healthy: true,
157+
Name: svcName,
158+
Port: 0,
159+
Healthy: true,
142160
},
143161
})
144162
}

pkg/discovery/dnssd/properties.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ type FallbackProperties struct {
6464
// Default is a golang template with single-line output to rewrite any service name into host.
6565
// The template data contains field ".ServiceName".
6666
// e.g. "{{.ServiceName}}.default.svc.cluster.local:8443"
67-
// This value is used when the service name is not applicable to any entry in Mappings .
67+
// This value is used when the service name is not applicable to any entry in Mappings.
68+
// The value can contain scheme, hostname and port, in which "hostname" is required.
69+
// e.g. "http://{{.ServiceName}}.default.svc.cluster.local:8443"
6870
Default string `json:"default"`
6971
}
7072

@@ -89,6 +91,8 @@ type HostMappingProperties struct {
8991
// Hosts is a list of known hosts. Each entry should be a golang template with single-line output.
9092
// The template data contains field ".ServiceName"
9193
// e.g. "pod-1.{{.ServiceName}}.default.svc.cluster.local:8989"
94+
// The value can contain scheme, hostname and port, in which "hostname" is required.
95+
// e.g. "http://{{.ServiceName}}.default.svc.cluster.local:8443"
9296
Hosts []string `json:"hosts"`
9397
}
9498

pkg/discovery/dnssd/testdata/bootstrap-test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ cloud:
1515
service: service[0-9]+
1616
hosts:
1717
- "inst-1.{{.ServiceName}}.test.mock:9999"
18-
- "inst-2.{{.ServiceName}}.test.mock:8888"
18+
- "inst-2.{{.ServiceName}}.test.mock"
19+
- "http://inst-3.{{.ServiceName}}.test.mock:9999"
20+
- "https://inst-4.{{.ServiceName}}.test.mock"

pkg/integrate/httpclient/client.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,13 @@ import (
2828
)
2929

3030
var (
31-
insecureInstanceMatcher = discovery.InstanceWithTagKV("secure", "false", true)
32-
supportedSchemes = utils.NewStringSet("http", "https")
31+
httpInstanceMatcher = discovery.InstanceWithTagKV("secure", "false", true).
32+
Or(discovery.InstanceWithTagKV("insecure", "true", true)).
33+
Or(discovery.InstanceWithMetaKV("scheme", "http"))
34+
httpsInstanceMatcher = discovery.InstanceWithTagKV("secure", "true", true).
35+
Or(discovery.InstanceWithTagKV("insecure", "false", true)).
36+
Or(discovery.InstanceWithMetaKV("scheme", "https"))
37+
supportedSchemes = utils.NewStringSet("http", "https")
3338
)
3439

3540
type clientDefaults struct {

pkg/integrate/httpclient/client_test.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func UpdateMockedSD(di *TestDI) test.SetupFunc {
6262
if port <= 0 {
6363
return ctx, nil
6464
}
65-
di.Client.UpdateMockedService(SDServiceNameFullInfo, sdtest.NthInstance(0), func(inst *discovery.Instance) {
65+
di.Client.UpdateMockedService(SDServiceNameFullInfo, sdtest.AnyHealthyInstance(), func(inst *discovery.Instance) {
6666
inst.Port = port
6767
})
6868
di.Client.UpdateMockedService(SDServiceNamePortOnly, sdtest.NthInstance(0), func(inst *discovery.Instance) {
@@ -130,7 +130,7 @@ func SubTestWithFullInfoSD(di *TestDI) test.GomegaSubTestFunc {
130130
}
131131
}
132132

133-
// SubTestWithPortOnlySD discovered service has no information about scheme and context-path, only has "port
133+
// SubTestWithPortOnlySD discovered service has no information about scheme and context-path, only has "port"
134134
func SubTestWithPortOnlySD(di *TestDI) test.GomegaSubTestFunc {
135135
return func(ctx context.Context, t *testing.T, g *gomega.WithT) {
136136
var client httpclient.Client
@@ -142,11 +142,10 @@ func SubTestWithPortOnlySD(di *TestDI) test.GomegaSubTestFunc {
142142
req := httpclient.NewRequest(TestPath, http.MethodPost, httpclient.WithBody(reqBody))
143143
_, e = client.Execute(ctx, req, httpclient.JsonBody(&EchoResponse{}))
144144
g.Expect(e).To(HaveOccurred(), "execution should fail without extra SD options")
145-
g.Expect(e).To(gomegautils.IsError(httpclient.ErrorTypeInternal), "error should be correct without extra SD options")
145+
g.Expect(e).To(gomegautils.IsError(httpclient.ErrorTypeResponse), "error should be correct without extra SD options")
146146

147147
// with proper SD options, should not fail
148148
client, e = di.HttpClient.WithService(SDServiceNamePortOnly, func(opt *httpclient.SDOption) {
149-
opt.Scheme = "http"
150149
opt.ContextPath = "/test"
151150
})
152151
g.Expect(e).To(Succeed(), "client with service name should be available")
@@ -399,12 +398,12 @@ func SubTestWithAbsoluteUrl(di *TestDI) test.GomegaSubTestFunc {
399398
random := utils.RandomString(20)
400399
now := time.Now().Format(time.RFC3339)
401400
reqBody := makeEchoRequestBody()
402-
opts := append([]httpclient.RequestOptions{
401+
opts := []httpclient.RequestOptions{
403402
httpclient.WithHeader("X-Data", random),
404403
httpclient.WithParam("time", now),
405404
httpclient.WithParam("data", random),
406405
httpclient.WithBody(reqBody),
407-
})
406+
}
408407

409408
uri, e := url.Parse(fmt.Sprintf(`http://localhost:%d%s`, webtest.CurrentPort(ctx), webtest.CurrentContextPath(ctx)))
410409
g.Expect(e).ToNot(HaveOccurred())

0 commit comments

Comments
 (0)