Skip to content

Commit

Permalink
balancer: add server loads from RPC trailers to DoneInfo (#2641)
Browse files Browse the repository at this point in the history
  • Loading branch information
menghanl authored Apr 2, 2019
1 parent 9244571 commit d389f9f
Show file tree
Hide file tree
Showing 8 changed files with 636 additions and 0 deletions.
5 changes: 5 additions & 0 deletions balancer/balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ type DoneInfo struct {
BytesSent bool
// BytesReceived indicates if any byte has been received from the server.
BytesReceived bool
// ServerLoad is the load received from server. It's usually sent as part of
// trailing metadata.
//
// The only supported type now is *orca_v1.LoadReport.
ServerLoad interface{}
}

var (
Expand Down
46 changes: 46 additions & 0 deletions internal/balancerload/load.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2019 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Package balancerload defines APIs to parse server loads in trailers. The
// parsed loads are sent to balancers in DoneInfo.
package balancerload

import (
"google.golang.org/grpc/metadata"
)

// Parser converts loads from metadata into a concrete type.
type Parser interface {
// Parse parses loads from metadata.
Parse(md metadata.MD) interface{}
}

var parser Parser

// SetParser sets the load parser.
//
// Not mutex-protected, should be called before any gRPC functions.
func SetParser(lr Parser) {
parser = lr
}

// Parse calls parser.Read().
func Parse(md metadata.MD) interface{} {
if parser == nil {
return nil
}
return parser.Parse(md)
}
84 changes: 84 additions & 0 deletions internal/balancerload/orca/orca.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2019 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

//go:generate protoc -I ./orca_v1 --go_out=plugins=grpc:./orca_v1 ./orca_v1/orca.proto

// Package orca implements Open Request Cost Aggregation.
package orca

import (
"github.com/golang/protobuf/proto"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/balancerload"
orcapb "google.golang.org/grpc/internal/balancerload/orca/orca_v1"
"google.golang.org/grpc/metadata"
)

const mdKey = "X-Endpoint-Load-Metrics-Bin"

// toBytes converts a orca load report into bytes.
func toBytes(r *orcapb.LoadReport) []byte {
if r == nil {
return nil
}

b, err := proto.Marshal(r)
if err != nil {
grpclog.Warningf("orca: failed to marshal load report: %v", err)
return nil
}
return b
}

// ToMetadata converts a orca load report into grpc metadata.
func ToMetadata(r *orcapb.LoadReport) metadata.MD {
b := toBytes(r)
if b == nil {
return nil
}
return metadata.Pairs(mdKey, string(b))
}

// fromBytes reads load report bytes and converts it to orca.
func fromBytes(b []byte) *orcapb.LoadReport {
ret := new(orcapb.LoadReport)
if err := proto.Unmarshal(b, ret); err != nil {
grpclog.Warningf("orca: failed to unmarshal load report: %v", err)
return nil
}
return ret
}

// FromMetadata reads load report from metadata and converts it to orca.
//
// It returns nil if report is not found in metadata.
func FromMetadata(md metadata.MD) *orcapb.LoadReport {
vs := md.Get(mdKey)
if len(vs) == 0 {
return nil
}
return fromBytes([]byte(vs[0]))
}

type loadParser struct{}

func (*loadParser) Parse(md metadata.MD) interface{} {
return FromMetadata(md)
}

func init() {
balancerload.SetParser(&loadParser{})
}
88 changes: 88 additions & 0 deletions internal/balancerload/orca/orca_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2019 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package orca

import (
"reflect"
"strings"
"testing"

"github.com/golang/protobuf/proto"
"google.golang.org/grpc/internal/balancerload/orca/orca_v1"
"google.golang.org/grpc/metadata"
)

var (
testMessage = &orca_v1.LoadReport{
CpuUtilization: 0.1,
MemUtilization: 0.2,
NicInUtilization: 0,
NicOutUtilization: 0,
RequestCostOrUtilization: map[string]float64{"ttt": 0.4},
}
testBytes, _ = proto.Marshal(testMessage)
)

func TestToMetadata(t *testing.T) {
tests := []struct {
name string
r *orca_v1.LoadReport
want metadata.MD
}{{
name: "nil",
r: nil,
want: nil,
}, {
name: "valid",
r: testMessage,
want: metadata.MD{
strings.ToLower(mdKey): []string{string(testBytes)},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ToMetadata(tt.r); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ToMetadata() = %v, want %v", got, tt.want)
}
})
}
}

func TestFromMetadata(t *testing.T) {
tests := []struct {
name string
md metadata.MD
want *orca_v1.LoadReport
}{{
name: "nil",
md: nil,
want: nil,
}, {
name: "valid",
md: metadata.MD{
strings.ToLower(mdKey): []string{string(testBytes)},
},
want: testMessage,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := FromMetadata(tt.md); !proto.Equal(got, tt.want) {
t.Errorf("FromMetadata() = %v, want %v", got, tt.want)
}
})
}
}
Loading

0 comments on commit d389f9f

Please sign in to comment.