Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Response Labels #9

Merged
merged 9 commits into from
Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/
# Dependency directories
vendor/
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,19 +271,21 @@ func (s *OddEvenRoutingStrategy) SelectRoute(
ctx context.Context,
req fiber.Request,
routes map[string]fiber.Component,
) (fiber.Component, []fiber.Component, error) {
) (fiber.Component, []fiber.Component, fiber.Labels, error) {
sessionIdStr := ""
if sessionHeader, ok := req.Header()["X-Session-ID"]; ok {
sessionIdStr = sessionHeader[0]
}
// Metadata that can be propagated upstream for logging / debugging
labels := fiber.NewLabelsMap()

if sessionID, err := strconv.Atoi(sessionIdStr); err != nil {
return nil, nil, err
} else {
if sessionID % 2 != 0 {
return routes["route-a"], []fiber.Component{}, nil
return routes["route-a"], []fiber.Component{}, labels.WithLabel("Match-Type", "even"), nil
} else {
return routes["route-b"], []fiber.Component{}, nil
return routes["route-b"], []fiber.Component{}, labels.WithLabel("Match-Type", "odd"), nil
}
}
}
Expand Down
22 changes: 12 additions & 10 deletions eager_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (fanIn *eagerRouterFanIn) Aggregate(
) Response {
// use routing strategy to fetch primary route and fallbacks
// publish the ordered routes into a channel
routesOrderCh, errCh := fanIn.strategy.getRoutesOrder(ctx, req, fanIn.router.GetRoutes())
routesOrderCh := fanIn.strategy.getRoutesOrder(ctx, req, fanIn.router.GetRoutes())

out := make(chan Response, 1)
go func() {
Expand All @@ -77,6 +77,9 @@ func (fanIn *eagerRouterFanIn) Aggregate(
// would be initialized from a routesOrderCh channel
routes []Component

// response labels
labels Labels = NewLabelsMap()

// index of current primary route
currentRouteIdx int

Expand All @@ -93,18 +96,17 @@ func (fanIn *eagerRouterFanIn) Aggregate(
} else {
responseCh = nil
}
case orderedRoutes, ok := <-routesOrderCh:
case routesOrderResponse, ok := <-routesOrderCh:
if ok {
routes = orderedRoutes
labels = routesOrderResponse.Labels
if routesOrderResponse.Err != nil {
masterResponse = NewErrorResponse(errors.NewFiberError(req.Protocol(), routesOrderResponse.Err))
} else {
routes = routesOrderResponse.Components
}
} else {
routesOrderCh = nil
}
case err, ok := <-errCh:
if ok {
masterResponse = NewErrorResponse(errors.NewFiberError(req.Protocol(), err))
} else {
errCh = nil
}
case <-ctx.Done():
if routes == nil {
// timeout exceeded, but no routes received. Sending error response
Expand Down Expand Up @@ -139,7 +141,7 @@ func (fanIn *eagerRouterFanIn) Aggregate(
}
}
}
out <- masterResponse
out <- masterResponse.WithLabels(labels)
}()

return <-out
Expand Down
8 changes: 6 additions & 2 deletions extras/random_routing_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package extras
import (
"context"
"math/rand"
"strconv"

"github.com/gojek/fiber"
)
Expand All @@ -19,8 +20,11 @@ func (s *RandomRoutingStrategy) SelectRoute(
_ context.Context,
_ fiber.Request,
routes map[string]fiber.Component,
) (route fiber.Component, fallbacks []fiber.Component, err error) {
) (route fiber.Component, fallbacks []fiber.Component, labels fiber.Labels, err error) {
idx := rand.Intn(len(routes))
// Add idx to attribute map for logging / debugging upstream
labels = fiber.NewLabelsMap().WithLabel("idx", strconv.Itoa(idx))

for _, child := range routes {
if idx == 0 {
route = child
Expand All @@ -29,5 +33,5 @@ func (s *RandomRoutingStrategy) SelectRoute(
}
idx--
}
return route, fallbacks, nil
return route, fallbacks, labels, nil
}
23 changes: 17 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
module github.com/gojek/fiber

go 1.14
go 1.18

require (
github.com/ghodss/yaml v1.0.0
github.com/google/go-cmp v0.5.8
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/opentracing/opentracing-go v1.1.0
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.8.0
go.uber.org/zap v1.17.0
google.golang.org/grpc v1.48.0
google.golang.org/protobuf v1.28.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.4.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
google.golang.org/grpc v1.48.0
google.golang.org/protobuf v1.28.0
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
3 changes: 0 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
Expand Down
27 changes: 26 additions & 1 deletion grpc/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,35 @@ func (r *Response) StatusCode() int {
return int(r.Status.Code())
}

// Label returns all the values associated with the given key, in the response metadata.
// If the key does not exist, an empty slice will be returned.
func (r *Response) Label(key string) []string {
return r.Metadata.Get(key)
}

// WithLabel appends the given value(s) to the key, in the response metadata.
// If the key does not already exist, a new key will be created.
// The modified response is returned.
func (r *Response) WithLabel(key string, values ...string) fiber.Response {
r.Metadata.Append(key, values...)
return r
}

// WithLabels does the same thing as WithLabel but over a collection of key-values.
func (r *Response) WithLabels(labels fiber.Labels) fiber.Response {
for _, key := range labels.Keys() {
values := labels.Label(key)
r.Metadata.Append(key, values...)
}
return r
}

func (r *Response) BackendName() string {
return strings.Join(r.Metadata.Get("backend"), ",")
return strings.Join(r.Label("backend"), ",")
leonlnj marked this conversation as resolved.
Show resolved Hide resolved
}

// WithBackendName sets the given backend name in the response metadata.
// The modified response is returned.
func (r *Response) WithBackendName(backendName string) fiber.Response {
r.Metadata.Set("backend", backendName)
return r
Expand Down
117 changes: 107 additions & 10 deletions grpc/response_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package grpc
package grpc_test

import (
"log"
"testing"

"github.com/gojek/fiber"
"github.com/gojek/fiber/grpc"
testproto "github.com/gojek/fiber/internal/testdata/gen/testdata/proto"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
Expand All @@ -15,16 +17,16 @@ import (
func TestResponse_Backend(t *testing.T) {
tests := []struct {
name string
res Response
want Response
res grpc.Response
want grpc.Response
backendName string
}{
{
name: "ok",
res: Response{
res: grpc.Response{
Metadata: map[string][]string{},
},
want: Response{
want: grpc.Response{
Metadata: metadata.New(map[string]string{"backend": "testing"}),
},
backendName: "testing",
Expand All @@ -42,21 +44,21 @@ func TestResponse_Backend(t *testing.T) {
func TestResponse_Status(t *testing.T) {
tests := []struct {
name string
res Response
res grpc.Response
expectedCode int
expectedSuccess bool
}{
{
name: "ok",
res: Response{
res: grpc.Response{
Status: *status.New(codes.OK, ""),
},
expectedCode: 0,
expectedSuccess: true,
},
{
name: "ok",
res: Response{
res: grpc.Response{
Status: *status.New(codes.InvalidArgument, ""),
},
expectedCode: 3,
Expand All @@ -83,12 +85,12 @@ func TestResponse_Payload(t *testing.T) {
responseByte, _ := proto.Marshal(response)
tests := []struct {
name string
req Response
req grpc.Response
expected []byte
}{
{
name: "",
req: Response{
req: grpc.Response{
Message: responseByte,
},
expected: responseByte,
Expand All @@ -100,3 +102,98 @@ func TestResponse_Payload(t *testing.T) {
})
}
}

func TestResponse_Label(t *testing.T) {
tests := map[string]struct {
response fiber.Response
key string
expected []string
}{
"empty labels": {
response: &grpc.Response{
Metadata: map[string][]string{},
},
key: "dummy-key",
},
"non-empty labels": {
response: &grpc.Response{
Metadata: map[string][]string{"key": []string{"v1", "v2"}},
},
key: "key",
expected: []string{"v1", "v2"},
},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
values := tt.response.Label(tt.key)
assert.Equal(t, tt.expected, values)
})
}
}

func TestResponse_WithLabel(t *testing.T) {
tests := map[string]struct {
response fiber.Response
key string
values []string
expected []string
}{
"new labels": {
response: &grpc.Response{
Metadata: map[string][]string{"key": []string{"v1", "v2"}},
},
key: "k1",
values: []string{"v1", "v2"},
expected: []string{"v1", "v2"},
},
"append labels": {
response: &grpc.Response{
Metadata: map[string][]string{"k1": []string{"v1", "v2"}},
},
key: "k1",
values: []string{"v3"},
expected: []string{"v1", "v2", "v3"},
},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
newLabels := tt.response.WithLabel(tt.key, tt.values...)
assert.Equal(t, tt.expected, newLabels.Label(tt.key))
})
}
}

func TestHTTPResponse_WithLabels(t *testing.T) {
tests := map[string]struct {
response fiber.Response
labels fiber.Labels
key string
expected []string
}{
"new labels": {
response: &grpc.Response{
Metadata: map[string][]string{"key": []string{"v1", "v2"}},
},
labels: fiber.LabelsMap{"k1": []string{"v1", "v2"}},
key: "k1",
expected: []string{"v1", "v2"},
},
"append labels": {
response: &grpc.Response{
Metadata: map[string][]string{"k1": []string{"v1", "v2"}},
},
labels: fiber.LabelsMap{"k1": []string{"v3"}},
key: "k1",
expected: []string{"v1", "v2", "v3"},
},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
newLabels := tt.response.WithLabels(tt.labels)
assert.Equal(t, tt.expected, newLabels.Label(tt.key))
})
}
}
Loading