forked from ebhomengo/niki
create account domain and impl loginorRegister for driver .
This commit is contained in:
parent
9ea2a5c493
commit
4fcaef0e28
|
|
@ -0,0 +1,53 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
pb "git.gocasts.ir/ebhomengo/niki/contract/goprotobuf/account"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/driverapp/service"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/pkg/types"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
Conn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(conn *grpc.ClientConn) *Client {
|
||||||
|
return &Client{
|
||||||
|
Conn: conn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) SendOTP(ctx context.Context, phoneNumber string) error {
|
||||||
|
|
||||||
|
client := pb.NewAccountServiceClient(c.Conn)
|
||||||
|
|
||||||
|
_, err := client.SendOtp(ctx, &pb.SendOtpRequest{
|
||||||
|
PhoneNumber: phoneNumber,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) LoginOrRegister(ctx context.Context, req service.LoginOrRegisterRequest) (service.LoginOrRegisterResponse, error) {
|
||||||
|
|
||||||
|
client := pb.NewAccountServiceClient(c.Conn)
|
||||||
|
|
||||||
|
res, err := client.LoginOrRegister(ctx, &pb.LoginOrRegisterRequest{
|
||||||
|
PhoneNumber: req.PhoneNumber,
|
||||||
|
VerifyCode: req.VerifyCode,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return service.LoginOrRegisterResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return service.LoginOrRegisterResponse{
|
||||||
|
ID: types.ID(res.Id),
|
||||||
|
PhoneNumber: res.PhoneNumber,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
var up bool
|
||||||
|
var down bool
|
||||||
|
|
||||||
|
var migrateCmd = &cobra.Command{
|
||||||
|
Use: "migrate",
|
||||||
|
Short: "Run database migrations",
|
||||||
|
Long: `This command runs the database migrations for the account service.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
migrate()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrate() {}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
migrateCmd.Flags().BoolVar(&up, "up", false, "Run migrations up")
|
||||||
|
migrateCmd.Flags().BoolVar(&down, "down", false, "Run migrations down")
|
||||||
|
RootCmd.AddCommand(migrateCmd)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
var RootCmd = &cobra.Command{
|
||||||
|
Use: "account_service",
|
||||||
|
Short: "A CLI for account Service",
|
||||||
|
Long: `account Service CLI is a tool to manage and run
|
||||||
|
the account service, including migrations and server startup.`,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
var serveCmd = &cobra.Command{
|
||||||
|
Use: "serve",
|
||||||
|
Short: "start a account service.",
|
||||||
|
Long: `This command starts the main account service.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
serve()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func serve() {}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/cmd/account/command"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := command.RootCmd.Execute(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/driverapp"
|
"git.gocasts.ir/ebhomengo/niki/driverapp"
|
||||||
cfgloader "git.gocasts.ir/ebhomengo/niki/pkg/cfg_loader2"
|
cfgloader "git.gocasts.ir/ebhomengo/niki/pkg/cfg_loader"
|
||||||
"git.gocasts.ir/ebhomengo/niki/pkg/migrator"
|
"git.gocasts.ir/ebhomengo/niki/pkg/migrator"
|
||||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
)
|
)
|
||||||
|
|
@ -46,7 +46,7 @@ func main() {
|
||||||
mgr.Up()
|
mgr.Up()
|
||||||
}
|
}
|
||||||
|
|
||||||
dapp := driverapp.Setup(cfg, conn)
|
//dapp := driverapp.Setup(cfg)
|
||||||
dapp.Start()
|
//dapp.Start()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,281 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v3.21.12
|
||||||
|
// source: contract/protobuf/account/account.proto
|
||||||
|
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginOrRegisterRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
PhoneNumber string `protobuf:"bytes,1,opt,name=phoneNumber,proto3" json:"phoneNumber,omitempty"`
|
||||||
|
VerifyCode string `protobuf:"bytes,2,opt,name=verifyCode,proto3" json:"verifyCode,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginOrRegisterRequest) Reset() {
|
||||||
|
*x = LoginOrRegisterRequest{}
|
||||||
|
mi := &file_contract_protobuf_account_account_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginOrRegisterRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LoginOrRegisterRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LoginOrRegisterRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_contract_protobuf_account_account_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use LoginOrRegisterRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LoginOrRegisterRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_contract_protobuf_account_account_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginOrRegisterRequest) GetPhoneNumber() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PhoneNumber
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginOrRegisterRequest) GetVerifyCode() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.VerifyCode
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginOrRegisterResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
PhoneNumber string `protobuf:"bytes,2,opt,name=phoneNumber,proto3" json:"phoneNumber,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginOrRegisterResponse) Reset() {
|
||||||
|
*x = LoginOrRegisterResponse{}
|
||||||
|
mi := &file_contract_protobuf_account_account_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginOrRegisterResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LoginOrRegisterResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LoginOrRegisterResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_contract_protobuf_account_account_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use LoginOrRegisterResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LoginOrRegisterResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_contract_protobuf_account_account_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginOrRegisterResponse) GetId() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginOrRegisterResponse) GetPhoneNumber() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PhoneNumber
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendOtpRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
PhoneNumber string `protobuf:"bytes,1,opt,name=phoneNumber,proto3" json:"phoneNumber,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SendOtpRequest) Reset() {
|
||||||
|
*x = SendOtpRequest{}
|
||||||
|
mi := &file_contract_protobuf_account_account_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SendOtpRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SendOtpRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SendOtpRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_contract_protobuf_account_account_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use SendOtpRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SendOtpRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_contract_protobuf_account_account_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SendOtpRequest) GetPhoneNumber() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PhoneNumber
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendOtpResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SendOtpResponse) Reset() {
|
||||||
|
*x = SendOtpResponse{}
|
||||||
|
mi := &file_contract_protobuf_account_account_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SendOtpResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SendOtpResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SendOtpResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_contract_protobuf_account_account_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use SendOtpResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SendOtpResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_contract_protobuf_account_account_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_contract_protobuf_account_account_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_contract_protobuf_account_account_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"'contract/protobuf/account/account.proto\x12\asendOtp\"Z\n" +
|
||||||
|
"\x16LoginOrRegisterRequest\x12 \n" +
|
||||||
|
"\vphoneNumber\x18\x01 \x01(\tR\vphoneNumber\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"verifyCode\x18\x02 \x01(\tR\n" +
|
||||||
|
"verifyCode\"K\n" +
|
||||||
|
"\x17LoginOrRegisterResponse\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\x04R\x02id\x12 \n" +
|
||||||
|
"\vphoneNumber\x18\x02 \x01(\tR\vphoneNumber\"2\n" +
|
||||||
|
"\x0eSendOtpRequest\x12 \n" +
|
||||||
|
"\vphoneNumber\x18\x01 \x01(\tR\vphoneNumber\"\x11\n" +
|
||||||
|
"\x0fSendOtpResponse2\xa4\x01\n" +
|
||||||
|
"\x0eAccountService\x12<\n" +
|
||||||
|
"\aSendOtp\x12\x17.sendOtp.SendOtpRequest\x1a\x18.sendOtp.SendOtpResponse\x12T\n" +
|
||||||
|
"\x0fLoginOrRegister\x12\x1f.sendOtp.LoginOrRegisterRequest\x1a .sendOtp.LoginOrRegisterResponseB\x1dZ\x1bcontract/goprotobuf/accountb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_contract_protobuf_account_account_proto_rawDescOnce sync.Once
|
||||||
|
file_contract_protobuf_account_account_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_contract_protobuf_account_account_proto_rawDescGZIP() []byte {
|
||||||
|
file_contract_protobuf_account_account_proto_rawDescOnce.Do(func() {
|
||||||
|
file_contract_protobuf_account_account_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_contract_protobuf_account_account_proto_rawDesc), len(file_contract_protobuf_account_account_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_contract_protobuf_account_account_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_contract_protobuf_account_account_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||||
|
var file_contract_protobuf_account_account_proto_goTypes = []any{
|
||||||
|
(*LoginOrRegisterRequest)(nil), // 0: sendOtp.LoginOrRegisterRequest
|
||||||
|
(*LoginOrRegisterResponse)(nil), // 1: sendOtp.LoginOrRegisterResponse
|
||||||
|
(*SendOtpRequest)(nil), // 2: sendOtp.SendOtpRequest
|
||||||
|
(*SendOtpResponse)(nil), // 3: sendOtp.SendOtpResponse
|
||||||
|
}
|
||||||
|
var file_contract_protobuf_account_account_proto_depIdxs = []int32{
|
||||||
|
2, // 0: sendOtp.AccountService.SendOtp:input_type -> sendOtp.SendOtpRequest
|
||||||
|
0, // 1: sendOtp.AccountService.LoginOrRegister:input_type -> sendOtp.LoginOrRegisterRequest
|
||||||
|
3, // 2: sendOtp.AccountService.SendOtp:output_type -> sendOtp.SendOtpResponse
|
||||||
|
1, // 3: sendOtp.AccountService.LoginOrRegister:output_type -> sendOtp.LoginOrRegisterResponse
|
||||||
|
2, // [2:4] is the sub-list for method output_type
|
||||||
|
0, // [0:2] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_contract_protobuf_account_account_proto_init() }
|
||||||
|
func file_contract_protobuf_account_account_proto_init() {
|
||||||
|
if File_contract_protobuf_account_account_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_contract_protobuf_account_account_proto_rawDesc), len(file_contract_protobuf_account_account_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 4,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_contract_protobuf_account_account_proto_goTypes,
|
||||||
|
DependencyIndexes: file_contract_protobuf_account_account_proto_depIdxs,
|
||||||
|
MessageInfos: file_contract_protobuf_account_account_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_contract_protobuf_account_account_proto = out.File
|
||||||
|
file_contract_protobuf_account_account_proto_goTypes = nil
|
||||||
|
file_contract_protobuf_account_account_proto_depIdxs = nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.6.1
|
||||||
|
// - protoc v3.21.12
|
||||||
|
// source: contract/protobuf/account/account.proto
|
||||||
|
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
AccountService_SendOtp_FullMethodName = "/sendOtp.AccountService/SendOtp"
|
||||||
|
AccountService_LoginOrRegister_FullMethodName = "/sendOtp.AccountService/LoginOrRegister"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccountServiceClient is the client API for AccountService service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type AccountServiceClient interface {
|
||||||
|
SendOtp(ctx context.Context, in *SendOtpRequest, opts ...grpc.CallOption) (*SendOtpResponse, error)
|
||||||
|
LoginOrRegister(ctx context.Context, in *LoginOrRegisterRequest, opts ...grpc.CallOption) (*LoginOrRegisterResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type accountServiceClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountServiceClient(cc grpc.ClientConnInterface) AccountServiceClient {
|
||||||
|
return &accountServiceClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *accountServiceClient) SendOtp(ctx context.Context, in *SendOtpRequest, opts ...grpc.CallOption) (*SendOtpResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(SendOtpResponse)
|
||||||
|
err := c.cc.Invoke(ctx, AccountService_SendOtp_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *accountServiceClient) LoginOrRegister(ctx context.Context, in *LoginOrRegisterRequest, opts ...grpc.CallOption) (*LoginOrRegisterResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(LoginOrRegisterResponse)
|
||||||
|
err := c.cc.Invoke(ctx, AccountService_LoginOrRegister_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountServiceServer is the server API for AccountService service.
|
||||||
|
// All implementations must embed UnimplementedAccountServiceServer
|
||||||
|
// for forward compatibility.
|
||||||
|
type AccountServiceServer interface {
|
||||||
|
SendOtp(context.Context, *SendOtpRequest) (*SendOtpResponse, error)
|
||||||
|
LoginOrRegister(context.Context, *LoginOrRegisterRequest) (*LoginOrRegisterResponse, error)
|
||||||
|
mustEmbedUnimplementedAccountServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedAccountServiceServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedAccountServiceServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedAccountServiceServer) SendOtp(context.Context, *SendOtpRequest) (*SendOtpResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method SendOtp not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedAccountServiceServer) LoginOrRegister(context.Context, *LoginOrRegisterRequest) (*LoginOrRegisterResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method LoginOrRegister not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedAccountServiceServer) mustEmbedUnimplementedAccountServiceServer() {}
|
||||||
|
func (UnimplementedAccountServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafeAccountServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to AccountServiceServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeAccountServiceServer interface {
|
||||||
|
mustEmbedUnimplementedAccountServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterAccountServiceServer(s grpc.ServiceRegistrar, srv AccountServiceServer) {
|
||||||
|
// If the following call panics, it indicates UnimplementedAccountServiceServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&AccountService_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _AccountService_SendOtp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SendOtpRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AccountServiceServer).SendOtp(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AccountService_SendOtp_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AccountServiceServer).SendOtp(ctx, req.(*SendOtpRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _AccountService_LoginOrRegister_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(LoginOrRegisterRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AccountServiceServer).LoginOrRegister(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AccountService_LoginOrRegister_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AccountServiceServer).LoginOrRegister(ctx, req.(*LoginOrRegisterRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountService_ServiceDesc is the grpc.ServiceDesc for AccountService service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var AccountService_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "sendOtp.AccountService",
|
||||||
|
HandlerType: (*AccountServiceServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "SendOtp",
|
||||||
|
Handler: _AccountService_SendOtp_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "LoginOrRegister",
|
||||||
|
Handler: _AccountService_LoginOrRegister_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "contract/protobuf/account/account.proto",
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
|
||||||
|
package sendOtp;
|
||||||
|
option go_package = "contract/goprotobuf/account";
|
||||||
|
|
||||||
|
message LoginOrRegisterRequest{
|
||||||
|
string phoneNumber = 1;
|
||||||
|
string verifyCode = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoginOrRegisterResponse {
|
||||||
|
uint64 id = 1;
|
||||||
|
string phoneNumber = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message SendOtpRequest {
|
||||||
|
string phoneNumber = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SendOtpResponse {}
|
||||||
|
|
||||||
|
service AccountService {
|
||||||
|
rpc SendOtp(SendOtpRequest) returns (SendOtpResponse);
|
||||||
|
rpc LoginOrRegister(LoginOrRegisterRequest) returns(LoginOrRegisterResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
service:
|
||||||
|
length_of_otp_code: 6
|
||||||
|
otp_chars: "0123456789"
|
||||||
|
otp_expire_time: 2
|
||||||
|
redis_db:
|
||||||
|
host:
|
||||||
|
port:
|
||||||
|
password:
|
||||||
|
db:
|
||||||
|
mysql_db:
|
||||||
|
username:
|
||||||
|
password:
|
||||||
|
port:
|
||||||
|
host:
|
||||||
|
db_name:
|
||||||
|
kavenegar:
|
||||||
|
api_key:
|
||||||
|
sender:
|
||||||
|
grpc_server:
|
||||||
|
port:
|
||||||
|
network:
|
||||||
|
grpc_client:
|
||||||
|
host:
|
||||||
|
port:
|
||||||
|
|
||||||
|
|
||||||
|
path_of_migration: ./account/repository/mysql/migration
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/adapter/kavenegar"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/adapter/redis"
|
||||||
|
grpcAccountDelivery "git.gocasts.ir/ebhomengo/niki/domain/account/delivery/grpc"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/account/repository/mysql"
|
||||||
|
redisRepo "git.gocasts.ir/ebhomengo/niki/domain/account/repository/redis"
|
||||||
|
database "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||||
|
rpcPkg "git.gocasts.ir/ebhomengo/niki/pkg/grpc"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/account/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Application struct {
|
||||||
|
GrpcServer grpcAccountDelivery.Server
|
||||||
|
Config Config
|
||||||
|
accountSvc service.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setup(cfg Config, db *database.DB) Application {
|
||||||
|
redisConn := redis.New(cfg.Redis)
|
||||||
|
otpRepo := redisRepo.NewRepositoryOtp(redisConn)
|
||||||
|
mysqlRepo := mysql.New(db)
|
||||||
|
smsAdapter := kavenegar.New(cfg.Kavenegar)
|
||||||
|
accountSvc := service.NewService(cfg.accountSvc, otpRepo, mysqlRepo, smsAdapter)
|
||||||
|
|
||||||
|
return Application{
|
||||||
|
accountSvc: accountSvc,
|
||||||
|
Config: cfg,
|
||||||
|
GrpcServer: grpcAccountDelivery.New(rpcPkg.New(), cfg.grpcServerCfg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) Start() {
|
||||||
|
err := app.GrpcServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error in serving GRPC server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/adapter/kavenegar"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/adapter/redis"
|
||||||
|
server "git.gocasts.ir/ebhomengo/niki/domain/account/delivery/grpc"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/account/service"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||||
|
client "git.gocasts.ir/ebhomengo/niki/pkg/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
accountSvc service.Config `koanf:"service"`
|
||||||
|
Redis redis.Config `koanf:"redis_db"`
|
||||||
|
MysqlDB mysql.Config `koanf:"mysql_db"`
|
||||||
|
Kavenegar kavenegar.Config `koanf:"kavenegar"`
|
||||||
|
grpcServerCfg server.Config `koanf:"grpc_server"`
|
||||||
|
grpcClientCfg client.Config `koanf:"grpc_client"`
|
||||||
|
PathOfMigration string `koanf:"path_of_migration"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
pb "git.gocasts.ir/ebhomengo/niki/contract/goprotobuf/account"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/account/service"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/pkg/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Port int `koanf:"port"`
|
||||||
|
Network string `koanf:"network"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
server grpc.RPCServer
|
||||||
|
config Config
|
||||||
|
svcAccount service.Service
|
||||||
|
pb.UnimplementedAccountServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(server grpc.RPCServer, cfg Config) Server {
|
||||||
|
return Server{
|
||||||
|
server: server,
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Server) SendOtp(ctx context.Context, req *pb.SendOtpRequest) (*pb.SendOtpResponse, error) {
|
||||||
|
err := s.svcAccount.SendOTP(ctx, req.PhoneNumber)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.SendOtpResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Server) LoginOrRegister(ctx context.Context, req *pb.LoginOrRegisterRequest) (*pb.LoginOrRegisterResponse, error) {
|
||||||
|
res := &pb.LoginOrRegisterResponse{}
|
||||||
|
driver, err := s.svcAccount.LoginOrRegisterDriver(ctx, req.PhoneNumber, req.VerifyCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id := uint64(driver.ID)
|
||||||
|
|
||||||
|
res.Id = id
|
||||||
|
res.PhoneNumber = driver.PhoneNumber
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Server) Start() error {
|
||||||
|
listener, err := net.Listen(s.config.Network, fmt.Sprintf(":%d", s.config.Port))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accountSvcServer := Server{}
|
||||||
|
|
||||||
|
pb.RegisterAccountServiceServer(s.server.Server, &accountSvcServer)
|
||||||
|
|
||||||
|
if err := s.server.Server.Serve(listener); err != nil {
|
||||||
|
log.Fatalf("failed to serve: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "git.gocasts.ir/ebhomengo/niki/pkg/types"
|
||||||
|
|
||||||
|
type Driver struct {
|
||||||
|
ID types.ID
|
||||||
|
PhoneNumber string
|
||||||
|
}
|
||||||
|
|
@ -6,11 +6,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/driverapp/service/entity"
|
"git.gocasts.ir/ebhomengo/niki/domain/account/entity"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||||
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
||||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
"git.gocasts.ir/ebhomengo/niki/pkg/types"
|
types "git.gocasts.ir/ebhomengo/niki/pkg/types"
|
||||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -18,20 +18,20 @@ const (
|
||||||
StatementKeyCreateDriver = iota + 1
|
StatementKeyCreateDriver = iota + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
type DriverRepo struct {
|
type AccountRepo struct {
|
||||||
conn *mysql.DB
|
db *mysql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(conn *mysql.DB) DriverRepo {
|
func New(db *mysql.DB) AccountRepo {
|
||||||
return DriverRepo{
|
return AccountRepo{
|
||||||
conn: conn,
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r DriverRepo) IsExistDriverByPhoneNumber(ctx context.Context, phoneNumber string) (bool, entity.Driver, error) {
|
func (r AccountRepo) IsExistDriverByPhoneNumber(ctx context.Context, phoneNumber string) (bool, entity.Driver, error) {
|
||||||
const op = "Repository.IsExistDriverByPhoneNumber"
|
const op = "Repository.IsExistDriverByPhoneNumber"
|
||||||
query := `select * from drivers where phone_number = ?`
|
query := `select * from drivers where phone_number = ?`
|
||||||
stmt, err := r.conn.PrepareStatement(ctx, StatementKeyIsExistDriverByPhoneNumber, query)
|
stmt, err := r.db.PrepareStatement(ctx, StatementKeyIsExistDriverByPhoneNumber, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, entity.Driver{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected).
|
return false, entity.Driver{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected).
|
||||||
WithMessage(errmsg.ErrorMsgCantPrepareStatement)
|
WithMessage(errmsg.ErrorMsgCantPrepareStatement)
|
||||||
|
|
@ -54,11 +54,11 @@ func (r DriverRepo) IsExistDriverByPhoneNumber(ctx context.Context, phoneNumber
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r DriverRepo) CreateDriver(ctx context.Context, driver entity.Driver) (entity.Driver, error) {
|
func (r AccountRepo) CreateDriver(ctx context.Context, driver entity.Driver) (entity.Driver, error) {
|
||||||
const op = "Repository.CreateDriver"
|
const op = "Repository.CreateDriver"
|
||||||
query := `insert into drivers(phone_number) values(?)`
|
query := `insert into drivers(phone_number) values(?)`
|
||||||
|
|
||||||
stmt, err := r.conn.PrepareStatement(ctx, StatementKeyCreateDriver, query)
|
stmt, err := r.db.PrepareStatement(ctx, StatementKeyCreateDriver, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entity.Driver{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected).
|
return entity.Driver{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected).
|
||||||
WithMessage(errmsg.ErrorMsgCantPrepareStatement)
|
WithMessage(errmsg.ErrorMsgCantPrepareStatement)
|
||||||
|
|
@ -81,9 +81,7 @@ func DriverScan(scanner mysql.Scanner) (entity.Driver, error) {
|
||||||
var createdAt, updatedAt time.Time
|
var createdAt, updatedAt time.Time
|
||||||
var driver entity.Driver
|
var driver entity.Driver
|
||||||
|
|
||||||
err := scanner.Scan(&driver.ID, &driver.FirstName, &driver.LastName,
|
err := scanner.Scan(&driver.ID, &driver.PhoneNumber, &createdAt, &updatedAt)
|
||||||
&driver.PhoneNumber, &driver.NationalCode, &driver.LicenseNumber,
|
|
||||||
&driver.BirthDate, &createdAt, &updatedAt)
|
|
||||||
|
|
||||||
return driver, err
|
return driver, err
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
-- +migrate Up
|
||||||
|
CREATE TABLE `drivers`(
|
||||||
|
`iD` INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`phone_number` VARCHAR(191) NOT NULL UNIQUE ,
|
||||||
|
|
||||||
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
DROP TABLE `drivers`;
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
smscontract "git.gocasts.ir/ebhomengo/niki/contract/sms"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/account/entity"
|
||||||
|
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
LengthOfOtpCode int `koanf:"length_of_otp_code"`
|
||||||
|
OtpChars string `koanf:"otp_chars"`
|
||||||
|
OtpExpireTime time.Duration `koanf:"otp_expire_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RepositoryOtp interface {
|
||||||
|
IsExistPhoneNumber(ctx context.Context, phoneNumber string) (bool, error)
|
||||||
|
SaveCodeWithPhoneNumber(ctx context.Context, phoneNumber string, code string, expireTime time.Duration) error
|
||||||
|
GetCodeByPhoneNumber(ctx context.Context, phoneNumber string) (string, error)
|
||||||
|
DeleteCodeByPhoneNumber(ctx context.Context, PhoneNumber string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
IsExistDriverByPhoneNumber(ctx context.Context, phoneNumber string) (bool, entity.Driver, error)
|
||||||
|
CreateDriver(ctx context.Context, driver entity.Driver) (entity.Driver, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
config Config
|
||||||
|
repositoryOtp RepositoryOtp
|
||||||
|
repository Repository
|
||||||
|
smsContract smscontract.SmsAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(cfg Config, repositoryOtp RepositoryOtp, repository Repository, smsContract smscontract.SmsAdapter) Service {
|
||||||
|
return Service{
|
||||||
|
config: cfg,
|
||||||
|
repositoryOtp: repositoryOtp,
|
||||||
|
repository: repository,
|
||||||
|
smsContract: smsContract,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) SendOTP(ctx context.Context, phoneNumber string) error {
|
||||||
|
const op = "accountService.SendOTP"
|
||||||
|
|
||||||
|
isExist, iErr := s.repositoryOtp.IsExistPhoneNumber(ctx, phoneNumber)
|
||||||
|
if iErr != nil {
|
||||||
|
return richerror.New(op).WithErr(iErr).WithKind(richerror.KindUnexpected)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isExist {
|
||||||
|
return richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeExist).WithKind(richerror.KindForbidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
newCode := s.generateVerificationCode()
|
||||||
|
sErr := s.repositoryOtp.SaveCodeWithPhoneNumber(ctx, phoneNumber, newCode, s.config.OtpExpireTime)
|
||||||
|
if sErr != nil {
|
||||||
|
return richerror.New(op).WithErr(sErr).WithKind(richerror.KindUnexpected)
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.smsContract.Send(phoneNumber, newCode)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) LoginOrRegisterDriver(ctx context.Context, phoneNumber string, verifyCode string) (entity.Driver, error) {
|
||||||
|
const op = "accountService.LoginOrRegisterDriver"
|
||||||
|
|
||||||
|
code, gErr := s.repositoryOtp.GetCodeByPhoneNumber(ctx, phoneNumber)
|
||||||
|
if gErr != nil {
|
||||||
|
return entity.Driver{}, richerror.New(op).WithErr(gErr).WithKind(richerror.KindUnexpected)
|
||||||
|
}
|
||||||
|
|
||||||
|
if code == "" || code != verifyCode {
|
||||||
|
return entity.Driver{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeIsNotValid).WithKind(richerror.KindForbidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, dErr := s.repositoryOtp.DeleteCodeByPhoneNumber(ctx, phoneNumber)
|
||||||
|
if dErr != nil {
|
||||||
|
return entity.Driver{}, richerror.New(op).WithErr(dErr).WithKind(richerror.KindUnexpected)
|
||||||
|
}
|
||||||
|
|
||||||
|
isExist, driver, eErr := s.repository.IsExistDriverByPhoneNumber(ctx, phoneNumber)
|
||||||
|
if eErr != nil {
|
||||||
|
return entity.Driver{}, richerror.New(op).WithErr(eErr).WithKind(richerror.KindUnexpected)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isExist {
|
||||||
|
newDriver, cErr := s.repository.CreateDriver(ctx, entity.Driver{
|
||||||
|
PhoneNumber: phoneNumber,
|
||||||
|
})
|
||||||
|
if cErr != nil {
|
||||||
|
return entity.Driver{}, richerror.New(op).WithErr(cErr).WithKind(richerror.KindUnexpected)
|
||||||
|
}
|
||||||
|
|
||||||
|
driver = newDriver
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) generateVerificationCode() string {
|
||||||
|
result := make([]byte, s.config.LengthOfOtpCode)
|
||||||
|
for i := 0; i < s.config.LengthOfOtpCode; i++ {
|
||||||
|
result[i] = s.config.OtpChars[rand.Intn(len(s.config.OtpChars))]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
@ -1,47 +1,38 @@
|
||||||
package driverapp
|
package driverapp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.gocasts.ir/ebhomengo/niki/adapter/kavenegar"
|
"git.gocasts.ir/ebhomengo/niki/adapter/account"
|
||||||
"git.gocasts.ir/ebhomengo/niki/adapter/redis"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/driverapp/delivery/http"
|
"git.gocasts.ir/ebhomengo/niki/driverapp/delivery/http"
|
||||||
repo "git.gocasts.ir/ebhomengo/niki/driverapp/repository/mysql"
|
|
||||||
otp "git.gocasts.ir/ebhomengo/niki/driverapp/repository/redis"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/driverapp/service"
|
"git.gocasts.ir/ebhomengo/niki/driverapp/service"
|
||||||
"git.gocasts.ir/ebhomengo/niki/pkg/http_server"
|
"git.gocasts.ir/ebhomengo/niki/pkg/http_server"
|
||||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
driverSvc service.Service
|
svc service.Service
|
||||||
driverRepo repo.DriverRepo
|
accountClient account.Client
|
||||||
driverRepoOtp otp.RepositoryOtp
|
handler http.Handler
|
||||||
driverHandler http.Handler
|
httpServer http.Server
|
||||||
driverHttpServer http.Server
|
config Config
|
||||||
driverConfig Config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Setup(config Config, conn *mysql.DB) Application {
|
func Setup(config Config, conn *grpc.ClientConn) Application {
|
||||||
driverRepo := repo.New(conn)
|
|
||||||
connRedis := redis.New(config.Redis)
|
|
||||||
driverRepoOtp := otp.NewRepositoryOtp(connRedis)
|
|
||||||
kavenegarSms := kavenegar.New(config.Kavenegar)
|
|
||||||
driverValidator := service.NewValidator()
|
driverValidator := service.NewValidator()
|
||||||
driverSvc := service.NewService(config.DriverSvc, driverRepoOtp, driverRepo, kavenegarSms, driverValidator)
|
accountClient := account.New(conn)
|
||||||
|
driverSvc := service.NewService(config.DriverSvc, accountClient, driverValidator)
|
||||||
driverHandler := http.NewHandler(driverSvc)
|
driverHandler := http.NewHandler(driverSvc)
|
||||||
|
|
||||||
httpServer := http_server.NewServer(config.HttpServer)
|
httpServer := http_server.NewServer(config.HttpServer)
|
||||||
|
|
||||||
return Application{
|
return Application{
|
||||||
driverSvc: driverSvc,
|
svc: driverSvc,
|
||||||
driverRepo: driverRepo,
|
handler: driverHandler,
|
||||||
driverRepoOtp: driverRepoOtp,
|
httpServer: http.New(httpServer, driverHandler),
|
||||||
driverHandler: driverHandler,
|
config: config,
|
||||||
driverHttpServer: http.New(httpServer, driverHandler),
|
|
||||||
driverConfig: config,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app Application) Start() {
|
func (app Application) Start() {
|
||||||
app.driverHttpServer.Serve()
|
app.httpServer.Serve()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
-- +migrate Up
|
|
||||||
CREATE TABLE `drivers`(
|
|
||||||
`iD` INT PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
`first_name` VARCHAR(191),
|
|
||||||
`last_name` VARCHAR(191),
|
|
||||||
`phone_number` VARCHAR(191) NOT NULL UNIQUE ,
|
|
||||||
`national_code` VARCHAR(191) UNIQUE ,
|
|
||||||
`license_number` VARCHAR(191) UNIQUE ,
|
|
||||||
`birth_date` TIMESTAMP,
|
|
||||||
|
|
||||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
-- +migrate Down
|
|
||||||
DROP TABLE `drivers`;
|
|
||||||
|
|
@ -8,15 +8,8 @@ type LoginOrRegisterRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginOrRegisterResponse struct {
|
type LoginOrRegisterResponse struct {
|
||||||
Data Data `json:"data"`
|
|
||||||
Token Token `json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Data struct {
|
|
||||||
ID types.ID `json:"id"`
|
ID types.ID `json:"id"`
|
||||||
PhoneNumber string `json:"phone_number"`
|
PhoneNumber string `json:"phone_number"`
|
||||||
FirstName string `json:"first_name"`
|
|
||||||
LastName string `json:"last_name"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,9 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
smscontract "git.gocasts.ir/ebhomengo/niki/contract/sms"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/driverapp/service/entity"
|
|
||||||
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
|
||||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -17,36 +14,28 @@ type Config struct {
|
||||||
OtpExpireTime time.Duration `koanf:"otp_expire_time"`
|
OtpExpireTime time.Duration `koanf:"otp_expire_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepositoryOtp interface {
|
type AccountClient interface {
|
||||||
IsExistPhoneNumber(ctx context.Context, phoneNumber string) (bool, error)
|
SendOTP(ctx context.Context, phoneNumber string) error
|
||||||
SaveCodeWithPhoneNumber(ctx context.Context, phoneNumber string, code string, expireTime time.Duration) error
|
LoginOrRegister(ctx context.Context, req LoginOrRegisterRequest) (LoginOrRegisterResponse, error)
|
||||||
GetCodeByPhoneNumber(ctx context.Context, phoneNumber string) (string, error)
|
|
||||||
DeleteCodeByPhoneNumber(ctx context.Context, PhoneNumber string) (bool, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Repository interface {
|
type AuthClient interface {
|
||||||
IsExistDriverByPhoneNumber(ctx context.Context, phoneNumber string) (bool, entity.Driver, error)
|
CreateAccessToken()
|
||||||
CreateDriver(ctx context.Context, driver entity.Driver) (entity.Driver, error)
|
CreateRefreshToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
config Config
|
config Config
|
||||||
repositoryOtp RepositoryOtp
|
accountClient AccountClient
|
||||||
repository Repository
|
|
||||||
smsContract smscontract.SmsAdapter
|
|
||||||
validator Validator
|
validator Validator
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(cfg Config,
|
func NewService(cfg Config,
|
||||||
repositoryOtp RepositoryOtp,
|
accountClient AccountClient,
|
||||||
repository Repository,
|
|
||||||
smsContract smscontract.SmsAdapter,
|
|
||||||
validator Validator) Service {
|
validator Validator) Service {
|
||||||
return Service{
|
return Service{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
repositoryOtp: repositoryOtp,
|
accountClient: accountClient,
|
||||||
repository: repository,
|
|
||||||
smsContract: smsContract,
|
|
||||||
validator: validator,
|
validator: validator,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,23 +46,12 @@ func (s Service) SendOtp(ctx context.Context, req SendOtpRequest) (SendOtpRespon
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SendOtpResponse{}, richerror.New(op).WithErr(err).WithMessage(err.Error())
|
return SendOtpResponse{}, richerror.New(op).WithErr(err).WithMessage(err.Error())
|
||||||
}
|
}
|
||||||
isExist, iErr := s.repositoryOtp.IsExistPhoneNumber(ctx, req.PhoneNumber)
|
|
||||||
if iErr != nil {
|
|
||||||
return SendOtpResponse{}, richerror.New(op).WithErr(iErr).WithKind(richerror.KindUnexpected)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isExist {
|
sErr := s.accountClient.SendOTP(ctx, req.PhoneNumber)
|
||||||
return SendOtpResponse{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeExist).WithKind(richerror.KindForbidden)
|
|
||||||
}
|
|
||||||
|
|
||||||
newCode := s.generateVerificationCode()
|
|
||||||
sErr := s.repositoryOtp.SaveCodeWithPhoneNumber(ctx, req.PhoneNumber, newCode, s.config.OtpExpireTime)
|
|
||||||
if sErr != nil {
|
if sErr != nil {
|
||||||
return SendOtpResponse{}, richerror.New(op).WithErr(sErr).WithKind(richerror.KindUnexpected)
|
return SendOtpResponse{}, richerror.New(op).WithErr(sErr).WithMessage(sErr.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
go s.smsContract.Send(req.PhoneNumber, newCode)
|
|
||||||
|
|
||||||
return SendOtpResponse{}, nil
|
return SendOtpResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,51 +63,14 @@ func (s Service) LoginOrRegister(ctx context.Context, req LoginOrRegisterRequest
|
||||||
return LoginOrRegisterResponse{}, richerror.New(op).WithErr(err).WithMessage(err.Error())
|
return LoginOrRegisterResponse{}, richerror.New(op).WithErr(err).WithMessage(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
code, gErr := s.repositoryOtp.GetCodeByPhoneNumber(ctx, req.PhoneNumber)
|
resp, lErr := s.accountClient.LoginOrRegister(ctx, req)
|
||||||
if gErr != nil {
|
if lErr != nil {
|
||||||
return LoginOrRegisterResponse{}, richerror.New(op).WithErr(gErr).WithKind(richerror.KindUnexpected)
|
return LoginOrRegisterResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if code == "" || code != req.VerifyCode {
|
fmt.Println("res:", resp)
|
||||||
return LoginOrRegisterResponse{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeIsNotValid).WithKind(richerror.KindForbidden)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, dErr := s.repositoryOtp.DeleteCodeByPhoneNumber(ctx, req.PhoneNumber)
|
|
||||||
if dErr != nil {
|
|
||||||
return LoginOrRegisterResponse{}, richerror.New(op).WithErr(dErr).WithKind(richerror.KindUnexpected)
|
|
||||||
}
|
|
||||||
|
|
||||||
isExist, driver, eErr := s.repository.IsExistDriverByPhoneNumber(ctx, req.PhoneNumber)
|
|
||||||
if eErr != nil {
|
|
||||||
return LoginOrRegisterResponse{}, richerror.New(op).WithErr(eErr).WithKind(richerror.KindUnexpected)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isExist {
|
|
||||||
newDriver, cErr := s.repository.CreateDriver(ctx, entity.Driver{
|
|
||||||
PhoneNumber: req.PhoneNumber,
|
|
||||||
})
|
|
||||||
if cErr != nil {
|
|
||||||
return LoginOrRegisterResponse{}, richerror.New(op).WithErr(cErr).WithKind(richerror.KindUnexpected)
|
|
||||||
}
|
|
||||||
|
|
||||||
driver = newDriver
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO : CreateAccessToken and create CreateRefreshToken
|
// TODO : CreateAccessToken and create CreateRefreshToken
|
||||||
|
|
||||||
return LoginOrRegisterResponse{
|
return LoginOrRegisterResponse{}, nil
|
||||||
Data: Data{
|
|
||||||
ID: driver.ID,
|
|
||||||
PhoneNumber: driver.PhoneNumber,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Service) generateVerificationCode() string {
|
|
||||||
result := make([]byte, s.config.LengthOfOtpCode)
|
|
||||||
for i := 0; i < s.config.LengthOfOtpCode; i++ {
|
|
||||||
result[i] = s.config.OtpChars[rand.Intn(len(s.config.OtpChars))]
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(result)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
6
go.mod
6
go.mod
|
|
@ -11,7 +11,6 @@ require (
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||||
github.com/kavenegar/kavenegar-go v0.0.0-20240205151018-77039f51467d
|
github.com/kavenegar/kavenegar-go v0.0.0-20240205151018-77039f51467d
|
||||||
github.com/knadh/koanf v1.5.0
|
github.com/knadh/koanf v1.5.0
|
||||||
github.com/knadh/koanf/v2 v2.3.0
|
|
||||||
github.com/labstack/echo-jwt/v4 v4.4.0
|
github.com/labstack/echo-jwt/v4 v4.4.0
|
||||||
github.com/labstack/echo/v4 v4.15.1
|
github.com/labstack/echo/v4 v4.15.1
|
||||||
github.com/ory/dockertest/v3 v3.12.0
|
github.com/ory/dockertest/v3 v3.12.0
|
||||||
|
|
@ -23,6 +22,8 @@ require (
|
||||||
github.com/swaggo/swag v1.16.6
|
github.com/swaggo/swag v1.16.6
|
||||||
golang.org/x/crypto v0.48.0
|
golang.org/x/crypto v0.48.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
|
google.golang.org/grpc v1.79.2
|
||||||
|
google.golang.org/protobuf v1.36.10
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -89,6 +90,9 @@ require (
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.35.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.42.0 // indirect
|
golang.org/x/tools v0.42.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect
|
||||||
|
google.golang.org/grpc v1.80.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
|
|
|
||||||
8
go.sum
8
go.sum
|
|
@ -160,6 +160,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
|
|
@ -535,7 +536,10 @@ google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRn
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
|
||||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
|
@ -544,6 +548,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||||
|
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||||
|
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
|
@ -555,6 +561,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
package cfgloader2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/knadh/koanf"
|
|
||||||
"github.com/knadh/koanf/parsers/yaml"
|
|
||||||
"github.com/knadh/koanf/providers/env"
|
|
||||||
"github.com/knadh/koanf/providers/file"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Option struct {
|
|
||||||
Prefix string
|
|
||||||
Delimiter string
|
|
||||||
Separator string
|
|
||||||
YamlFilePath string
|
|
||||||
CallbackEnv func(string) string
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultCallbackEnv(source, prefix, separator string) string {
|
|
||||||
base := strings.ToLower(strings.TrimPrefix(source, prefix))
|
|
||||||
return strings.ReplaceAll(base, separator, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Load(options Option, config interface{}) error {
|
|
||||||
k := koanf.New(options.Delimiter)
|
|
||||||
|
|
||||||
// Load configuration from YAML file if provided
|
|
||||||
if options.YamlFilePath != "" {
|
|
||||||
if err := k.Load(file.Provider(options.YamlFilePath), yaml.Parser()); err != nil {
|
|
||||||
log.Fatalf("Error loading config file: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback := options.CallbackEnv
|
|
||||||
if callback == nil {
|
|
||||||
// Set default callback using the prefix and separator from options
|
|
||||||
callback = func(source string) string {
|
|
||||||
return defaultCallbackEnv(source, options.Prefix, options.Separator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load environment variables with the specified prefix and callback
|
|
||||||
if err := k.Load(env.Provider(options.Prefix, options.Delimiter, callback), nil); err != nil {
|
|
||||||
log.Fatalf("Error loading environment variables: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal into provided config structure (passing address)
|
|
||||||
if err := k.Unmarshal("", &config); err != nil {
|
|
||||||
log.Fatalf("Error unmarshalling config: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Host string `koanf:"host"`
|
||||||
|
Port int `koanf:"port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(cfg Config) (*grpc.ClientConn, error) {
|
||||||
|
address := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
|
||||||
|
grpcConn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return grpcConn, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import "google.golang.org/grpc"
|
||||||
|
|
||||||
|
type RPCServer struct {
|
||||||
|
Server *grpc.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() RPCServer {
|
||||||
|
return RPCServer{
|
||||||
|
Server: grpc.NewServer(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s RPCServer) Stop() {
|
||||||
|
s.Server.GracefulStop()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
||||||
203
vendor/google.golang.org/genproto/googleapis/rpc/status/status.pb.go
generated
vendored
Normal file
203
vendor/google.golang.org/genproto/googleapis/rpc/status/status.pb.go
generated
vendored
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
// Copyright 2025 Google LLC
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.26.0
|
||||||
|
// protoc v4.24.4
|
||||||
|
// source: google/rpc/status.proto
|
||||||
|
|
||||||
|
package status
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
anypb "google.golang.org/protobuf/types/known/anypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
// The `Status` type defines a logical error model that is suitable for
|
||||||
|
// different programming environments, including REST APIs and RPC APIs. It is
|
||||||
|
// used by [gRPC](https://github.com/grpc). Each `Status` message contains
|
||||||
|
// three pieces of data: error code, error message, and error details.
|
||||||
|
//
|
||||||
|
// You can find out more about this error model and how to work with it in the
|
||||||
|
// [API Design Guide](https://cloud.google.com/apis/design/errors).
|
||||||
|
type Status struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// The status code, which should be an enum value of
|
||||||
|
// [google.rpc.Code][google.rpc.Code].
|
||||||
|
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
|
||||||
|
// A developer-facing error message, which should be in English. Any
|
||||||
|
// user-facing error message should be localized and sent in the
|
||||||
|
// [google.rpc.Status.details][google.rpc.Status.details] field, or localized
|
||||||
|
// by the client.
|
||||||
|
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||||
|
// A list of messages that carry the error details. There is a common set of
|
||||||
|
// message types for APIs to use.
|
||||||
|
Details []*anypb.Any `protobuf:"bytes,3,rep,name=details,proto3" json:"details,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Status) Reset() {
|
||||||
|
*x = Status{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_google_rpc_status_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Status) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Status) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Status) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_google_rpc_status_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Status.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Status) Descriptor() ([]byte, []int) {
|
||||||
|
return file_google_rpc_status_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Status) GetCode() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Code
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Status) GetMessage() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Message
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Status) GetDetails() []*anypb.Any {
|
||||||
|
if x != nil {
|
||||||
|
return x.Details
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_google_rpc_status_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_google_rpc_status_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x17, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61,
|
||||||
|
0x74, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||||
|
0x65, 0x2e, 0x72, 0x70, 0x63, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
|
0x22, 0x66, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f,
|
||||||
|
0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18,
|
||||||
|
0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61,
|
||||||
|
0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||||
|
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52,
|
||||||
|
0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x42, 0x61, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e,
|
||||||
|
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x42, 0x0b, 0x53, 0x74, 0x61, 0x74,
|
||||||
|
0x75, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x37, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||||
|
0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x65, 0x6e,
|
||||||
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 0x73,
|
||||||
|
0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x3b, 0x73, 0x74, 0x61, 0x74,
|
||||||
|
0x75, 0x73, 0xf8, 0x01, 0x01, 0xa2, 0x02, 0x03, 0x52, 0x50, 0x43, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||||
|
0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_google_rpc_status_proto_rawDescOnce sync.Once
|
||||||
|
file_google_rpc_status_proto_rawDescData = file_google_rpc_status_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_google_rpc_status_proto_rawDescGZIP() []byte {
|
||||||
|
file_google_rpc_status_proto_rawDescOnce.Do(func() {
|
||||||
|
file_google_rpc_status_proto_rawDescData = protoimpl.X.CompressGZIP(file_google_rpc_status_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_google_rpc_status_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_google_rpc_status_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
|
var file_google_rpc_status_proto_goTypes = []interface{}{
|
||||||
|
(*Status)(nil), // 0: google.rpc.Status
|
||||||
|
(*anypb.Any)(nil), // 1: google.protobuf.Any
|
||||||
|
}
|
||||||
|
var file_google_rpc_status_proto_depIdxs = []int32{
|
||||||
|
1, // 0: google.rpc.Status.details:type_name -> google.protobuf.Any
|
||||||
|
1, // [1:1] is the sub-list for method output_type
|
||||||
|
1, // [1:1] is the sub-list for method input_type
|
||||||
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
|
0, // [0:1] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_google_rpc_status_proto_init() }
|
||||||
|
func file_google_rpc_status_proto_init() {
|
||||||
|
if File_google_rpc_status_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_google_rpc_status_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Status); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_google_rpc_status_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 1,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_google_rpc_status_proto_goTypes,
|
||||||
|
DependencyIndexes: file_google_rpc_status_proto_depIdxs,
|
||||||
|
MessageInfos: file_google_rpc_status_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_google_rpc_status_proto = out.File
|
||||||
|
file_google_rpc_status_proto_rawDesc = nil
|
||||||
|
file_google_rpc_status_proto_goTypes = nil
|
||||||
|
file_google_rpc_status_proto_depIdxs = nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Google Inc.
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
## Community Code of Conduct
|
||||||
|
|
||||||
|
gRPC follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
# How to contribute
|
||||||
|
|
||||||
|
We welcome your patches and contributions to gRPC! Please read the gRPC
|
||||||
|
organization's [governance
|
||||||
|
rules](https://github.com/grpc/grpc-community/blob/master/governance.md) before
|
||||||
|
proceeding.
|
||||||
|
|
||||||
|
If you are new to GitHub, please start by reading [Pull Request howto](https://help.github.com/articles/about-pull-requests/)
|
||||||
|
|
||||||
|
## Legal requirements
|
||||||
|
|
||||||
|
In order to protect both you and ourselves, you will need to sign the
|
||||||
|
[Contributor License
|
||||||
|
Agreement](https://identity.linuxfoundation.org/projects/cncf). When you create
|
||||||
|
your first PR, a link will be added as a comment that contains the steps needed
|
||||||
|
to complete this process.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
A great way to start is by searching through our open issues. [Unassigned issues
|
||||||
|
labeled as "help
|
||||||
|
wanted"](https://github.com/grpc/grpc-go/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3A%22Status%3A%20Help%20Wanted%22%20no%3Aassignee)
|
||||||
|
are especially nice for first-time contributors, as they should be well-defined
|
||||||
|
problems that already have agreed-upon solutions.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
We follow [Google's published Go style
|
||||||
|
guide](https://google.github.io/styleguide/go/). Note that there are three
|
||||||
|
primary documents that make up this style guide; please follow them as closely
|
||||||
|
as possible. If a reviewer recommends something that contradicts those
|
||||||
|
guidelines, there may be valid reasons to do so, but it should be rare.
|
||||||
|
|
||||||
|
## Guidelines for Pull Requests
|
||||||
|
|
||||||
|
Please read the following carefully to ensure your contributions can be merged
|
||||||
|
smoothly and quickly.
|
||||||
|
|
||||||
|
### PR Contents
|
||||||
|
|
||||||
|
- Create **small PRs** that are narrowly focused on **addressing a single
|
||||||
|
concern**. We often receive PRs that attempt to fix several things at the same
|
||||||
|
time, and if one part of the PR has a problem, that will hold up the entire
|
||||||
|
PR.
|
||||||
|
|
||||||
|
- If your change does not address an **open issue** with an **agreed
|
||||||
|
resolution**, consider opening an issue and discussing it first. If you are
|
||||||
|
suggesting a behavioral or API change, consider starting with a [gRFC
|
||||||
|
proposal](https://github.com/grpc/proposal). Many new features that are not
|
||||||
|
bug fixes will require cross-language agreement.
|
||||||
|
|
||||||
|
- If you want to fix **formatting or style**, consider whether your changes are
|
||||||
|
an obvious improvement or might be considered a personal preference. If a
|
||||||
|
style change is based on preference, it likely will not be accepted. If it
|
||||||
|
corrects widely agreed-upon anti-patterns, then please do create a PR and
|
||||||
|
explain the benefits of the change.
|
||||||
|
|
||||||
|
- For correcting **misspellings**, please be aware that we use some terms that
|
||||||
|
are sometimes flagged by spell checkers. As an example, "if an only if" is
|
||||||
|
often written as "iff". Please do not make spelling correction changes unless
|
||||||
|
you are certain they are misspellings.
|
||||||
|
|
||||||
|
- **All tests need to be passing** before your change can be merged. We
|
||||||
|
recommend you run tests locally before creating your PR to catch breakages
|
||||||
|
early on:
|
||||||
|
|
||||||
|
- `./scripts/vet.sh` to catch vet errors.
|
||||||
|
- `go test -cpu 1,4 -timeout 7m ./...` to run the tests.
|
||||||
|
- `go test -race -cpu 1,4 -timeout 7m ./...` to run tests in race mode.
|
||||||
|
|
||||||
|
Note that we have a multi-module repo, so `go test` commands may need to be
|
||||||
|
run from the root of each module in order to cause all tests to run.
|
||||||
|
|
||||||
|
*Alternatively*, you may find it easier to push your changes to your fork on
|
||||||
|
GitHub, which will trigger a GitHub Actions run that you can use to verify
|
||||||
|
everything is passing.
|
||||||
|
|
||||||
|
- Note that there are two GitHub actions checks that need not be green:
|
||||||
|
|
||||||
|
1. We test the freshness of the generated proto code we maintain via the
|
||||||
|
`vet-proto` check. If the source proto files are updated, but our repo is
|
||||||
|
not updated, an optional checker will fail. This will be fixed by our team
|
||||||
|
in a separate PR and will not prevent the merge of your PR.
|
||||||
|
|
||||||
|
2. We run a checker that will fail if there is any change in dependencies of
|
||||||
|
an exported package via the `dependencies` check. If new dependencies are
|
||||||
|
added that are not appropriate, we may not accept your PR (see below).
|
||||||
|
|
||||||
|
- If you are adding a **new file**, make sure it has the **copyright message**
|
||||||
|
template at the top as a comment. You can copy the message from an existing
|
||||||
|
file and update the year.
|
||||||
|
|
||||||
|
- The grpc package should only depend on standard Go packages and a small number
|
||||||
|
of exceptions. **If your contribution introduces new dependencies**, you will
|
||||||
|
need a discussion with gRPC-Go maintainers.
|
||||||
|
|
||||||
|
### PR Descriptions
|
||||||
|
|
||||||
|
- **PR titles** should start with the name of the component being addressed, or
|
||||||
|
the type of change. Examples: transport, client, server, round_robin, xds,
|
||||||
|
cleanup, deps.
|
||||||
|
|
||||||
|
- Read and follow the **guidelines for PR titles and descriptions** here:
|
||||||
|
https://google.github.io/eng-practices/review/developer/cl-descriptions.html
|
||||||
|
|
||||||
|
*particularly* the sections "First Line" and "Body is Informative".
|
||||||
|
|
||||||
|
Note: your PR description will be used as the git commit message in a
|
||||||
|
squash-and-merge if your PR is approved. We may make changes to this as
|
||||||
|
necessary.
|
||||||
|
|
||||||
|
- **Does this PR relate to an open issue?** On the first line, please use the
|
||||||
|
tag `Fixes #<issue>` to ensure the issue is closed when the PR is merged. Or
|
||||||
|
use `Updates #<issue>` if the PR is related to an open issue, but does not fix
|
||||||
|
it. Consider filing an issue if one does not already exist.
|
||||||
|
|
||||||
|
- PR descriptions *must* conclude with **release notes** as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
RELEASE NOTES:
|
||||||
|
* <component>: <summary>
|
||||||
|
```
|
||||||
|
|
||||||
|
This need not match the PR title.
|
||||||
|
|
||||||
|
The summary must:
|
||||||
|
|
||||||
|
* be something that gRPC users will understand.
|
||||||
|
|
||||||
|
* clearly explain the feature being added, the issue being fixed, or the
|
||||||
|
behavior being changed, etc. If fixing a bug, be clear about how the bug
|
||||||
|
can be triggered by an end-user.
|
||||||
|
|
||||||
|
* begin with a capital letter and use complete sentences.
|
||||||
|
|
||||||
|
* be as short as possible to describe the change being made.
|
||||||
|
|
||||||
|
If a PR is *not* end-user visible -- e.g. a cleanup, testing change, or
|
||||||
|
GitHub-related, use `RELEASE NOTES: n/a`.
|
||||||
|
|
||||||
|
### PR Process
|
||||||
|
|
||||||
|
- Please **self-review** your code changes before sending your PR. This will
|
||||||
|
prevent simple, obvious errors from causing delays.
|
||||||
|
|
||||||
|
- Maintain a **clean commit history** and use **meaningful commit messages**.
|
||||||
|
PRs with messy commit histories are difficult to review and won't be merged.
|
||||||
|
Before sending your PR, ensure your changes are based on top of the latest
|
||||||
|
`upstream/master` commits, and avoid rebasing in the middle of a code review.
|
||||||
|
You should **never use `git push -f`** unless absolutely necessary during a
|
||||||
|
review, as it can interfere with GitHub's tracking of comments.
|
||||||
|
|
||||||
|
- Unless your PR is trivial, you should **expect reviewer comments** that you
|
||||||
|
will need to address before merging. We'll label the PR as `Status: Requires
|
||||||
|
Reporter Clarification` if we expect you to respond to these comments in a
|
||||||
|
timely manner. If the PR remains inactive for 6 days, it will be marked as
|
||||||
|
`stale`, and we will automatically close it after 7 days if we don't hear back
|
||||||
|
from you. Please feel free to ping issues or bugs if you do not get a response
|
||||||
|
within a week.
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
This repository is governed by the gRPC organization's [governance rules](https://github.com/grpc/grpc-community/blob/master/governance.md).
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
This page lists all active maintainers of this repository. If you were a
|
||||||
|
maintainer and would like to add your name to the Emeritus list, please send us a
|
||||||
|
PR.
|
||||||
|
|
||||||
|
See [GOVERNANCE.md](https://github.com/grpc/grpc-community/blob/master/governance.md)
|
||||||
|
for governance guidelines and how to become a maintainer.
|
||||||
|
See [CONTRIBUTING.md](https://github.com/grpc/grpc-community/blob/master/CONTRIBUTING.md)
|
||||||
|
for general contribution guidelines.
|
||||||
|
|
||||||
|
## Maintainers (in alphabetical order)
|
||||||
|
|
||||||
|
- [arjan-bal](https://github.com/arjan-bal), Google LLC
|
||||||
|
- [arvindbr8](https://github.com/arvindbr8), Google LLC
|
||||||
|
- [atollena](https://github.com/atollena), Datadog, Inc.
|
||||||
|
- [dfawley](https://github.com/dfawley), Google LLC
|
||||||
|
- [easwars](https://github.com/easwars), Google LLC
|
||||||
|
- [gtcooke94](https://github.com/gtcooke94), Google LLC
|
||||||
|
|
||||||
|
## Emeritus Maintainers (in alphabetical order)
|
||||||
|
- [adelez](https://github.com/adelez)
|
||||||
|
- [aranjans](https://github.com/aranjans)
|
||||||
|
- [canguler](https://github.com/canguler)
|
||||||
|
- [cesarghali](https://github.com/cesarghali)
|
||||||
|
- [erm-g](https://github.com/erm-g)
|
||||||
|
- [iamqizhao](https://github.com/iamqizhao)
|
||||||
|
- [jeanbza](https://github.com/jeanbza)
|
||||||
|
- [jtattermusch](https://github.com/jtattermusch)
|
||||||
|
- [lyuxuan](https://github.com/lyuxuan)
|
||||||
|
- [makmukhi](https://github.com/makmukhi)
|
||||||
|
- [matt-kwong](https://github.com/matt-kwong)
|
||||||
|
- [menghanl](https://github.com/menghanl)
|
||||||
|
- [nicolasnoble](https://github.com/nicolasnoble)
|
||||||
|
- [purnesh42h](https://github.com/purnesh42h)
|
||||||
|
- [srini100](https://github.com/srini100)
|
||||||
|
- [yongni](https://github.com/yongni)
|
||||||
|
- [zasweq](https://github.com/zasweq)
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
all: vet test testrace
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build google.golang.org/grpc/...
|
||||||
|
|
||||||
|
clean:
|
||||||
|
go clean -i google.golang.org/grpc/...
|
||||||
|
|
||||||
|
deps:
|
||||||
|
GO111MODULE=on go get -d -v google.golang.org/grpc/...
|
||||||
|
|
||||||
|
proto:
|
||||||
|
@ if ! which protoc > /dev/null; then \
|
||||||
|
echo "error: protoc not installed" >&2; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
go generate google.golang.org/grpc/...
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test -cpu 1,4 -timeout 7m google.golang.org/grpc/...
|
||||||
|
|
||||||
|
testsubmodule:
|
||||||
|
cd security/advancedtls && go test -cpu 1,4 -timeout 7m google.golang.org/grpc/security/advancedtls/...
|
||||||
|
cd security/authorization && go test -cpu 1,4 -timeout 7m google.golang.org/grpc/security/authorization/...
|
||||||
|
|
||||||
|
testrace:
|
||||||
|
go test -race -cpu 1,4 -timeout 7m google.golang.org/grpc/...
|
||||||
|
|
||||||
|
testdeps:
|
||||||
|
GO111MODULE=on go get -d -v -t google.golang.org/grpc/...
|
||||||
|
|
||||||
|
vet: vetdeps
|
||||||
|
./scripts/vet.sh
|
||||||
|
|
||||||
|
vetdeps:
|
||||||
|
./scripts/vet.sh -install
|
||||||
|
|
||||||
|
.PHONY: \
|
||||||
|
all \
|
||||||
|
build \
|
||||||
|
clean \
|
||||||
|
deps \
|
||||||
|
proto \
|
||||||
|
test \
|
||||||
|
testsubmodule \
|
||||||
|
testrace \
|
||||||
|
testdeps \
|
||||||
|
vet \
|
||||||
|
vetdeps
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright 2014 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.
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
# gRPC-Go
|
||||||
|
|
||||||
|
[][API]
|
||||||
|
[](https://goreportcard.com/report/github.com/grpc/grpc-go)
|
||||||
|
[](https://codecov.io/gh/grpc/grpc-go)
|
||||||
|
|
||||||
|
The [Go][] implementation of [gRPC][]: A high performance, open source, general
|
||||||
|
RPC framework that puts mobile and HTTP/2 first. For more information see the
|
||||||
|
[Go gRPC docs][], or jump directly into the [quick start][].
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **[Go][]**: any one of the **two latest major** [releases][go-releases].
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Simply add the following import to your code, and then `go [build|run|test]`
|
||||||
|
will automatically fetch the necessary dependencies:
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "google.golang.org/grpc"
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** If you are trying to access `grpc-go` from **China**, see the
|
||||||
|
> [FAQ](#FAQ) below.
|
||||||
|
|
||||||
|
## Learn more
|
||||||
|
|
||||||
|
- [Go gRPC docs][], which include a [quick start][] and [API
|
||||||
|
reference][API] among other resources
|
||||||
|
- [Low-level technical docs](Documentation) from this repository
|
||||||
|
- [Performance benchmark][]
|
||||||
|
- [Examples](examples)
|
||||||
|
- [Contribution guidelines](CONTRIBUTING.md)
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### I/O Timeout Errors
|
||||||
|
|
||||||
|
The `golang.org` domain may be blocked from some countries. `go get` usually
|
||||||
|
produces an error like the following when this happens:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ go get -u google.golang.org/grpc
|
||||||
|
package google.golang.org/grpc: unrecognized import path "google.golang.org/grpc" (https fetch: Get https://google.golang.org/grpc?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)
|
||||||
|
```
|
||||||
|
|
||||||
|
To build Go code, there are several options:
|
||||||
|
|
||||||
|
- Set up a VPN and access google.golang.org through that.
|
||||||
|
|
||||||
|
- With Go module support: it is possible to use the `replace` feature of `go
|
||||||
|
mod` to create aliases for golang.org packages. In your project's directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go mod edit -replace=google.golang.org/grpc=github.com/grpc/grpc-go@latest
|
||||||
|
go mod tidy
|
||||||
|
go mod vendor
|
||||||
|
go build -mod=vendor
|
||||||
|
```
|
||||||
|
|
||||||
|
Again, this will need to be done for all transitive dependencies hosted on
|
||||||
|
golang.org as well. For details, refer to [golang/go issue
|
||||||
|
#28652](https://github.com/golang/go/issues/28652).
|
||||||
|
|
||||||
|
### Compiling error, undefined: grpc.SupportPackageIsVersion
|
||||||
|
|
||||||
|
Please update to the latest version of gRPC-Go using
|
||||||
|
`go get google.golang.org/grpc`.
|
||||||
|
|
||||||
|
### How to turn on logging
|
||||||
|
|
||||||
|
The default logger is controlled by environment variables. Turn everything on
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ export GRPC_GO_LOG_VERBOSITY_LEVEL=99
|
||||||
|
$ export GRPC_GO_LOG_SEVERITY_LEVEL=info
|
||||||
|
```
|
||||||
|
|
||||||
|
### The RPC failed with error `"code = Unavailable desc = transport is closing"`
|
||||||
|
|
||||||
|
This error means the connection the RPC is using was closed, and there are many
|
||||||
|
possible reasons, including:
|
||||||
|
1. mis-configured transport credentials, connection failed on handshaking
|
||||||
|
1. bytes disrupted, possibly by a proxy in between
|
||||||
|
1. server shutdown
|
||||||
|
1. Keepalive parameters caused connection shutdown, for example if you have
|
||||||
|
configured your server to terminate connections regularly to [trigger DNS
|
||||||
|
lookups](https://github.com/grpc/grpc-go/issues/3170#issuecomment-552517779).
|
||||||
|
If this is the case, you may want to increase your
|
||||||
|
[MaxConnectionAgeGrace](https://pkg.go.dev/google.golang.org/grpc/keepalive?tab=doc#ServerParameters),
|
||||||
|
to allow longer RPC calls to finish.
|
||||||
|
|
||||||
|
It can be tricky to debug this because the error happens on the client side but
|
||||||
|
the root cause of the connection being closed is on the server side. Turn on
|
||||||
|
logging on __both client and server__, and see if there are any transport
|
||||||
|
errors.
|
||||||
|
|
||||||
|
[API]: https://pkg.go.dev/google.golang.org/grpc
|
||||||
|
[Go]: https://golang.org
|
||||||
|
[Go module]: https://github.com/golang/go/wiki/Modules
|
||||||
|
[gRPC]: https://grpc.io
|
||||||
|
[Go gRPC docs]: https://grpc.io/docs/languages/go
|
||||||
|
[Performance benchmark]: https://performance-dot-grpc-testing.appspot.com/explore?dashboard=5180705743044608
|
||||||
|
[quick start]: https://grpc.io/docs/languages/go/quickstart
|
||||||
|
[go-releases]: https://golang.org/doc/devel/release.html
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
For information on gRPC Security Policy and reporting potential security issues, please see [gRPC CVE Process](https://github.com/grpc/proposal/blob/master/P4-grpc-cve-process.md).
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 attributes defines a generic key/value store used in various gRPC
|
||||||
|
// components.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This package is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
package attributes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attributes is an immutable struct for storing and retrieving generic
|
||||||
|
// key/value pairs. Keys must be hashable, and users should define their own
|
||||||
|
// types for keys. Values should not be modified after they are added to an
|
||||||
|
// Attributes or if they were received from one. If values implement 'Equal(o
|
||||||
|
// any) bool', it will be called by (*Attributes).Equal to determine whether
|
||||||
|
// two values with the same key should be considered equal.
|
||||||
|
type Attributes struct {
|
||||||
|
m map[any]any
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Attributes containing the key/value pair.
|
||||||
|
func New(key, value any) *Attributes {
|
||||||
|
return &Attributes{m: map[any]any{key: value}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValue returns a new Attributes containing the previous keys and values
|
||||||
|
// and the new key/value pair. If the same key appears multiple times, the
|
||||||
|
// last value overwrites all previous values for that key. To remove an
|
||||||
|
// existing key, use a nil value. value should not be modified later.
|
||||||
|
func (a *Attributes) WithValue(key, value any) *Attributes {
|
||||||
|
if a == nil {
|
||||||
|
return New(key, value)
|
||||||
|
}
|
||||||
|
n := &Attributes{m: make(map[any]any, len(a.m)+1)}
|
||||||
|
for k, v := range a.m {
|
||||||
|
n.m[k] = v
|
||||||
|
}
|
||||||
|
n.m[key] = value
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the value associated with these attributes for key, or nil if
|
||||||
|
// no value is associated with key. The returned value should not be modified.
|
||||||
|
func (a *Attributes) Value(key any) any {
|
||||||
|
if a == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return a.m[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns whether a and o are equivalent. If 'Equal(o any) bool' is
|
||||||
|
// implemented for a value in the attributes, it is called to determine if the
|
||||||
|
// value matches the one stored in the other attributes. If Equal is not
|
||||||
|
// implemented, standard equality is used to determine if the two values are
|
||||||
|
// equal. Note that some types (e.g. maps) aren't comparable by default, so
|
||||||
|
// they must be wrapped in a struct, or in an alias type, with Equal defined.
|
||||||
|
func (a *Attributes) Equal(o *Attributes) bool {
|
||||||
|
if a == nil && o == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if a == nil || o == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(a.m) != len(o.m) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for k, v := range a.m {
|
||||||
|
ov, ok := o.m[k]
|
||||||
|
if !ok {
|
||||||
|
// o missing element of a
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if eq, ok := v.(interface{ Equal(o any) bool }); ok {
|
||||||
|
if !eq.Equal(ov) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if v != ov {
|
||||||
|
// Fallback to a standard equality check if Value is unimplemented.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// String prints the attribute map. If any key or values throughout the map
|
||||||
|
// implement fmt.Stringer, it calls that method and appends.
|
||||||
|
func (a *Attributes) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("{")
|
||||||
|
first := true
|
||||||
|
for k, v := range a.m {
|
||||||
|
if !first {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("%q: %q ", str(k), str(v)))
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
sb.WriteString("}")
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func str(x any) (s string) {
|
||||||
|
if v, ok := x.(fmt.Stringer); ok {
|
||||||
|
return fmt.Sprint(v)
|
||||||
|
} else if v, ok := x.(string); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("<%p>", x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON helps implement the json.Marshaler interface, thereby rendering
|
||||||
|
// the Attributes correctly when printing (via pretty.JSON) structs containing
|
||||||
|
// Attributes as fields.
|
||||||
|
//
|
||||||
|
// Is it impossible to unmarshal attributes from a JSON representation and this
|
||||||
|
// method is meant only for debugging purposes.
|
||||||
|
func (a *Attributes) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(a.String()), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// See internal/backoff package for the backoff implementation. This file is
|
||||||
|
// kept for the exported types and API backward compatibility.
|
||||||
|
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/backoff"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultBackoffConfig uses values specified for backoff in
|
||||||
|
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
|
||||||
|
//
|
||||||
|
// Deprecated: use ConnectParams instead. Will be supported throughout 1.x.
|
||||||
|
var DefaultBackoffConfig = BackoffConfig{
|
||||||
|
MaxDelay: 120 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackoffConfig defines the parameters for the default gRPC backoff strategy.
|
||||||
|
//
|
||||||
|
// Deprecated: use ConnectParams instead. Will be supported throughout 1.x.
|
||||||
|
type BackoffConfig struct {
|
||||||
|
// MaxDelay is the upper bound of backoff delay.
|
||||||
|
MaxDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectParams defines the parameters for connecting and retrying. Users are
|
||||||
|
// encouraged to use this instead of the BackoffConfig type defined above. See
|
||||||
|
// here for more details:
|
||||||
|
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
type ConnectParams struct {
|
||||||
|
// Backoff specifies the configuration options for connection backoff.
|
||||||
|
Backoff backoff.Config
|
||||||
|
// MinConnectTimeout is the minimum amount of time we are willing to give a
|
||||||
|
// connection to complete.
|
||||||
|
MinConnectTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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 backoff provides configuration options for backoff.
|
||||||
|
//
|
||||||
|
// More details can be found at:
|
||||||
|
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
|
||||||
|
//
|
||||||
|
// All APIs in this package are experimental.
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Config defines the configuration options for backoff.
|
||||||
|
type Config struct {
|
||||||
|
// BaseDelay is the amount of time to backoff after the first failure.
|
||||||
|
BaseDelay time.Duration
|
||||||
|
// Multiplier is the factor with which to multiply backoffs after a
|
||||||
|
// failed retry. Should ideally be greater than 1.
|
||||||
|
Multiplier float64
|
||||||
|
// Jitter is the factor with which backoffs are randomized.
|
||||||
|
Jitter float64
|
||||||
|
// MaxDelay is the upper bound of backoff delay.
|
||||||
|
MaxDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultConfig is a backoff configuration with the default values specified
|
||||||
|
// at https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
|
||||||
|
//
|
||||||
|
// This should be useful for callers who want to configure backoff with
|
||||||
|
// non-default values only for a subset of the options.
|
||||||
|
var DefaultConfig = Config{
|
||||||
|
BaseDelay: 1.0 * time.Second,
|
||||||
|
Multiplier: 1.6,
|
||||||
|
Jitter: 0.2,
|
||||||
|
MaxDelay: 120 * time.Second,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,392 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 balancer defines APIs for load balancing in gRPC.
|
||||||
|
// All APIs in this package are experimental.
|
||||||
|
package balancer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/channelz"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
estats "google.golang.org/grpc/experimental/stats"
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
"google.golang.org/grpc/internal"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
"google.golang.org/grpc/serviceconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// m is a map from name to balancer builder.
|
||||||
|
m = make(map[string]Builder)
|
||||||
|
|
||||||
|
logger = grpclog.Component("balancer")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register registers the balancer builder to the balancer map. b.Name
|
||||||
|
// (lowercased) will be used as the name registered with this builder. If the
|
||||||
|
// Builder implements ConfigParser, ParseConfig will be called when new service
|
||||||
|
// configs are received by the resolver, and the result will be provided to the
|
||||||
|
// Balancer in UpdateClientConnState.
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple Balancers are
|
||||||
|
// registered with the same name, the one registered last will take effect.
|
||||||
|
func Register(b Builder) {
|
||||||
|
name := strings.ToLower(b.Name())
|
||||||
|
if name != b.Name() {
|
||||||
|
// TODO: Skip the use of strings.ToLower() to index the map after v1.59
|
||||||
|
// is released to switch to case sensitive balancer registry. Also,
|
||||||
|
// remove this warning and update the docstrings for Register and Get.
|
||||||
|
logger.Warningf("Balancer registered with name %q. grpc-go will be switching to case sensitive balancer registries soon", b.Name())
|
||||||
|
}
|
||||||
|
m[name] = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// unregisterForTesting deletes the balancer with the given name from the
|
||||||
|
// balancer map.
|
||||||
|
//
|
||||||
|
// This function is not thread-safe.
|
||||||
|
func unregisterForTesting(name string) {
|
||||||
|
delete(m, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
internal.BalancerUnregister = unregisterForTesting
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the resolver builder registered with the given name.
|
||||||
|
// Note that the compare is done in a case-insensitive fashion.
|
||||||
|
// If no builder is register with the name, nil will be returned.
|
||||||
|
func Get(name string) Builder {
|
||||||
|
if strings.ToLower(name) != name {
|
||||||
|
// TODO: Skip the use of strings.ToLower() to index the map after v1.59
|
||||||
|
// is released to switch to case sensitive balancer registry. Also,
|
||||||
|
// remove this warning and update the docstrings for Register and Get.
|
||||||
|
logger.Warningf("Balancer retrieved for name %q. grpc-go will be switching to case sensitive balancer registries soon", name)
|
||||||
|
}
|
||||||
|
if b, ok := m[strings.ToLower(name)]; ok {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSubConnOptions contains options to create new SubConn.
|
||||||
|
type NewSubConnOptions struct {
|
||||||
|
// CredsBundle is the credentials bundle that will be used in the created
|
||||||
|
// SubConn. If it's nil, the original creds from grpc DialOptions will be
|
||||||
|
// used.
|
||||||
|
//
|
||||||
|
// Deprecated: Use the Attributes field in resolver.Address to pass
|
||||||
|
// arbitrary data to the credential handshaker.
|
||||||
|
CredsBundle credentials.Bundle
|
||||||
|
// HealthCheckEnabled indicates whether health check service should be
|
||||||
|
// enabled on this SubConn
|
||||||
|
HealthCheckEnabled bool
|
||||||
|
// StateListener is called when the state of the subconn changes. If nil,
|
||||||
|
// Balancer.UpdateSubConnState will be called instead. Will never be
|
||||||
|
// invoked until after Connect() is called on the SubConn created with
|
||||||
|
// these options.
|
||||||
|
StateListener func(SubConnState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// State contains the balancer's state relevant to the gRPC ClientConn.
|
||||||
|
type State struct {
|
||||||
|
// State contains the connectivity state of the balancer, which is used to
|
||||||
|
// determine the state of the ClientConn.
|
||||||
|
ConnectivityState connectivity.State
|
||||||
|
// Picker is used to choose connections (SubConns) for RPCs.
|
||||||
|
Picker Picker
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientConn represents a gRPC ClientConn.
|
||||||
|
//
|
||||||
|
// This interface is to be implemented by gRPC. Users should not need a
|
||||||
|
// brand new implementation of this interface. For the situations like
|
||||||
|
// testing, the new implementation should embed this interface. This allows
|
||||||
|
// gRPC to add new methods to this interface.
|
||||||
|
//
|
||||||
|
// NOTICE: This interface is intended to be implemented by gRPC, or intercepted
|
||||||
|
// by custom load balancing polices. Users should not need their own complete
|
||||||
|
// implementation of this interface -- they should always delegate to a
|
||||||
|
// ClientConn passed to Builder.Build() by embedding it in their
|
||||||
|
// implementations. An embedded ClientConn must never be nil, or runtime panics
|
||||||
|
// will occur.
|
||||||
|
type ClientConn interface {
|
||||||
|
// NewSubConn is called by balancer to create a new SubConn.
|
||||||
|
// It doesn't block and wait for the connections to be established.
|
||||||
|
// Behaviors of the SubConn can be controlled by options.
|
||||||
|
//
|
||||||
|
// Deprecated: please be aware that in a future version, SubConns will only
|
||||||
|
// support one address per SubConn.
|
||||||
|
NewSubConn([]resolver.Address, NewSubConnOptions) (SubConn, error)
|
||||||
|
// RemoveSubConn removes the SubConn from ClientConn.
|
||||||
|
// The SubConn will be shutdown.
|
||||||
|
//
|
||||||
|
// Deprecated: use SubConn.Shutdown instead.
|
||||||
|
RemoveSubConn(SubConn)
|
||||||
|
// UpdateAddresses updates the addresses used in the passed in SubConn.
|
||||||
|
// gRPC checks if the currently connected address is still in the new list.
|
||||||
|
// If so, the connection will be kept. Else, the connection will be
|
||||||
|
// gracefully closed, and a new connection will be created.
|
||||||
|
//
|
||||||
|
// This may trigger a state transition for the SubConn.
|
||||||
|
//
|
||||||
|
// Deprecated: this method will be removed. Create new SubConns for new
|
||||||
|
// addresses instead.
|
||||||
|
UpdateAddresses(SubConn, []resolver.Address)
|
||||||
|
|
||||||
|
// UpdateState notifies gRPC that the balancer's internal state has
|
||||||
|
// changed.
|
||||||
|
//
|
||||||
|
// gRPC will update the connectivity state of the ClientConn, and will call
|
||||||
|
// Pick on the new Picker to pick new SubConns.
|
||||||
|
UpdateState(State)
|
||||||
|
|
||||||
|
// ResolveNow is called by balancer to notify gRPC to do a name resolving.
|
||||||
|
ResolveNow(resolver.ResolveNowOptions)
|
||||||
|
|
||||||
|
// Target returns the dial target for this ClientConn.
|
||||||
|
//
|
||||||
|
// Deprecated: Use the Target field in the BuildOptions instead.
|
||||||
|
Target() string
|
||||||
|
|
||||||
|
// MetricsRecorder provides the metrics recorder that balancers can use to
|
||||||
|
// record metrics. Balancer implementations which do not register metrics on
|
||||||
|
// metrics registry and record on them can ignore this method. The returned
|
||||||
|
// MetricsRecorder is guaranteed to never be nil.
|
||||||
|
MetricsRecorder() estats.MetricsRecorder
|
||||||
|
|
||||||
|
// EnforceClientConnEmbedding is included to force implementers to embed
|
||||||
|
// another implementation of this interface, allowing gRPC to add methods
|
||||||
|
// without breaking users.
|
||||||
|
internal.EnforceClientConnEmbedding
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildOptions contains additional information for Build.
|
||||||
|
type BuildOptions struct {
|
||||||
|
// DialCreds is the transport credentials to use when communicating with a
|
||||||
|
// remote load balancer server. Balancer implementations which do not
|
||||||
|
// communicate with a remote load balancer server can ignore this field.
|
||||||
|
DialCreds credentials.TransportCredentials
|
||||||
|
// CredsBundle is the credentials bundle to use when communicating with a
|
||||||
|
// remote load balancer server. Balancer implementations which do not
|
||||||
|
// communicate with a remote load balancer server can ignore this field.
|
||||||
|
CredsBundle credentials.Bundle
|
||||||
|
// Dialer is the custom dialer to use when communicating with a remote load
|
||||||
|
// balancer server. Balancer implementations which do not communicate with a
|
||||||
|
// remote load balancer server can ignore this field.
|
||||||
|
Dialer func(context.Context, string) (net.Conn, error)
|
||||||
|
// Authority is the server name to use as part of the authentication
|
||||||
|
// handshake when communicating with a remote load balancer server. Balancer
|
||||||
|
// implementations which do not communicate with a remote load balancer
|
||||||
|
// server can ignore this field.
|
||||||
|
Authority string
|
||||||
|
// ChannelzParent is the parent ClientConn's channelz channel.
|
||||||
|
ChannelzParent channelz.Identifier
|
||||||
|
// CustomUserAgent is the custom user agent set on the parent ClientConn.
|
||||||
|
// The balancer should set the same custom user agent if it creates a
|
||||||
|
// ClientConn.
|
||||||
|
CustomUserAgent string
|
||||||
|
// Target contains the parsed address info of the dial target. It is the
|
||||||
|
// same resolver.Target as passed to the resolver. See the documentation for
|
||||||
|
// the resolver.Target type for details about what it contains.
|
||||||
|
Target resolver.Target
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder creates a balancer.
|
||||||
|
type Builder interface {
|
||||||
|
// Build creates a new balancer with the ClientConn.
|
||||||
|
Build(cc ClientConn, opts BuildOptions) Balancer
|
||||||
|
// Name returns the name of balancers built by this builder.
|
||||||
|
// It will be used to pick balancers (for example in service config).
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigParser parses load balancer configs.
|
||||||
|
type ConfigParser interface {
|
||||||
|
// ParseConfig parses the JSON load balancer config provided into an
|
||||||
|
// internal form or returns an error if the config is invalid. For future
|
||||||
|
// compatibility reasons, unknown fields in the config should be ignored.
|
||||||
|
ParseConfig(LoadBalancingConfigJSON json.RawMessage) (serviceconfig.LoadBalancingConfig, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PickInfo contains additional information for the Pick operation.
|
||||||
|
type PickInfo struct {
|
||||||
|
// FullMethodName is the method name that NewClientStream() is called
|
||||||
|
// with. The canonical format is /service/Method.
|
||||||
|
FullMethodName string
|
||||||
|
// Ctx is the RPC's context, and may contain relevant RPC-level information
|
||||||
|
// like the outgoing header metadata.
|
||||||
|
Ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoneInfo contains additional information for done.
|
||||||
|
type DoneInfo struct {
|
||||||
|
// Err is the rpc error the RPC finished with. It could be nil.
|
||||||
|
Err error
|
||||||
|
// Trailer contains the metadata from the RPC's trailer, if present.
|
||||||
|
Trailer metadata.MD
|
||||||
|
// BytesSent indicates if any bytes have been sent to the server.
|
||||||
|
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_v3.LoadReport.
|
||||||
|
ServerLoad any
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNoSubConnAvailable indicates no SubConn is available for pick().
|
||||||
|
// gRPC will block the RPC until a new picker is available via UpdateState().
|
||||||
|
ErrNoSubConnAvailable = errors.New("no SubConn is available")
|
||||||
|
// ErrTransientFailure indicates all SubConns are in TransientFailure.
|
||||||
|
// WaitForReady RPCs will block, non-WaitForReady RPCs will fail.
|
||||||
|
//
|
||||||
|
// Deprecated: return an appropriate error based on the last resolution or
|
||||||
|
// connection attempt instead. The behavior is the same for any non-gRPC
|
||||||
|
// status error.
|
||||||
|
ErrTransientFailure = errors.New("all SubConns are in TransientFailure")
|
||||||
|
)
|
||||||
|
|
||||||
|
// PickResult contains information related to a connection chosen for an RPC.
|
||||||
|
type PickResult struct {
|
||||||
|
// SubConn is the connection to use for this pick, if its state is Ready.
|
||||||
|
// If the state is not Ready, gRPC will block the RPC until a new Picker is
|
||||||
|
// provided by the balancer (using ClientConn.UpdateState). The SubConn
|
||||||
|
// must be one returned by ClientConn.NewSubConn.
|
||||||
|
SubConn SubConn
|
||||||
|
|
||||||
|
// Done is called when the RPC is completed. If the SubConn is not ready,
|
||||||
|
// this will be called with a nil parameter. If the SubConn is not a valid
|
||||||
|
// type, Done may not be called. May be nil if the balancer does not wish
|
||||||
|
// to be notified when the RPC completes.
|
||||||
|
Done func(DoneInfo)
|
||||||
|
|
||||||
|
// Metadata provides a way for LB policies to inject arbitrary per-call
|
||||||
|
// metadata. Any metadata returned here will be merged with existing
|
||||||
|
// metadata added by the client application.
|
||||||
|
//
|
||||||
|
// LB policies with child policies are responsible for propagating metadata
|
||||||
|
// injected by their children to the ClientConn, as part of Pick().
|
||||||
|
Metadata metadata.MD
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransientFailureError returns e. It exists for backward compatibility and
|
||||||
|
// will be deleted soon.
|
||||||
|
//
|
||||||
|
// Deprecated: no longer necessary, picker errors are treated this way by
|
||||||
|
// default.
|
||||||
|
func TransientFailureError(e error) error { return e }
|
||||||
|
|
||||||
|
// Picker is used by gRPC to pick a SubConn to send an RPC.
|
||||||
|
// Balancer is expected to generate a new picker from its snapshot every time its
|
||||||
|
// internal state has changed.
|
||||||
|
//
|
||||||
|
// The pickers used by gRPC can be updated by ClientConn.UpdateState().
|
||||||
|
type Picker interface {
|
||||||
|
// Pick returns the connection to use for this RPC and related information.
|
||||||
|
//
|
||||||
|
// Pick should not block. If the balancer needs to do I/O or any blocking
|
||||||
|
// or time-consuming work to service this call, it should return
|
||||||
|
// ErrNoSubConnAvailable, and the Pick call will be repeated by gRPC when
|
||||||
|
// the Picker is updated (using ClientConn.UpdateState).
|
||||||
|
//
|
||||||
|
// If an error is returned:
|
||||||
|
//
|
||||||
|
// - If the error is ErrNoSubConnAvailable, gRPC will block until a new
|
||||||
|
// Picker is provided by the balancer (using ClientConn.UpdateState).
|
||||||
|
//
|
||||||
|
// - If the error is a status error (implemented by the grpc/status
|
||||||
|
// package), gRPC will terminate the RPC with the code and message
|
||||||
|
// provided.
|
||||||
|
//
|
||||||
|
// - For all other errors, wait for ready RPCs will wait, but non-wait for
|
||||||
|
// ready RPCs will be terminated with this error's Error() string and
|
||||||
|
// status code Unavailable.
|
||||||
|
Pick(info PickInfo) (PickResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Balancer takes input from gRPC, manages SubConns, and collects and aggregates
|
||||||
|
// the connectivity states.
|
||||||
|
//
|
||||||
|
// It also generates and updates the Picker used by gRPC to pick SubConns for RPCs.
|
||||||
|
//
|
||||||
|
// UpdateClientConnState, ResolverError, UpdateSubConnState, and Close are
|
||||||
|
// guaranteed to be called synchronously from the same goroutine. There's no
|
||||||
|
// guarantee on picker.Pick, it may be called anytime.
|
||||||
|
type Balancer interface {
|
||||||
|
// UpdateClientConnState is called by gRPC when the state of the ClientConn
|
||||||
|
// changes. If the error returned is ErrBadResolverState, the ClientConn
|
||||||
|
// will begin calling ResolveNow on the active name resolver with
|
||||||
|
// exponential backoff until a subsequent call to UpdateClientConnState
|
||||||
|
// returns a nil error. Any other errors are currently ignored.
|
||||||
|
UpdateClientConnState(ClientConnState) error
|
||||||
|
// ResolverError is called by gRPC when the name resolver reports an error.
|
||||||
|
ResolverError(error)
|
||||||
|
// UpdateSubConnState is called by gRPC when the state of a SubConn
|
||||||
|
// changes.
|
||||||
|
//
|
||||||
|
// Deprecated: Use NewSubConnOptions.StateListener when creating the
|
||||||
|
// SubConn instead.
|
||||||
|
UpdateSubConnState(SubConn, SubConnState)
|
||||||
|
// Close closes the balancer. The balancer is not currently required to
|
||||||
|
// call SubConn.Shutdown for its existing SubConns; however, this will be
|
||||||
|
// required in a future release, so it is recommended.
|
||||||
|
Close()
|
||||||
|
// ExitIdle instructs the LB policy to reconnect to backends / exit the
|
||||||
|
// IDLE state, if appropriate and possible. Note that SubConns that enter
|
||||||
|
// the IDLE state will not reconnect until SubConn.Connect is called.
|
||||||
|
ExitIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitIdler is an optional interface for balancers to implement. If
|
||||||
|
// implemented, ExitIdle will be called when ClientConn.Connect is called, if
|
||||||
|
// the ClientConn is idle. If unimplemented, ClientConn.Connect will cause
|
||||||
|
// all SubConns to connect.
|
||||||
|
//
|
||||||
|
// Deprecated: All balancers must implement this interface. This interface will
|
||||||
|
// be removed in a future release.
|
||||||
|
type ExitIdler interface {
|
||||||
|
// ExitIdle instructs the LB policy to reconnect to backends / exit the
|
||||||
|
// IDLE state, if appropriate and possible. Note that SubConns that enter
|
||||||
|
// the IDLE state will not reconnect until SubConn.Connect is called.
|
||||||
|
ExitIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientConnState describes the state of a ClientConn relevant to the
|
||||||
|
// balancer.
|
||||||
|
type ClientConnState struct {
|
||||||
|
ResolverState resolver.State
|
||||||
|
// The parsed load balancing configuration returned by the builder's
|
||||||
|
// ParseConfig method, if implemented.
|
||||||
|
BalancerConfig serviceconfig.LoadBalancingConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrBadResolverState may be returned by UpdateClientConnState to indicate a
|
||||||
|
// problem with the provided name resolver data.
|
||||||
|
var ErrBadResolverState = errors.New("bad resolver state")
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = grpclog.Component("balancer")
|
||||||
|
|
||||||
|
type baseBuilder struct {
|
||||||
|
name string
|
||||||
|
pickerBuilder PickerBuilder
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb *baseBuilder) Build(cc balancer.ClientConn, _ balancer.BuildOptions) balancer.Balancer {
|
||||||
|
bal := &baseBalancer{
|
||||||
|
cc: cc,
|
||||||
|
pickerBuilder: bb.pickerBuilder,
|
||||||
|
|
||||||
|
subConns: resolver.NewAddressMapV2[balancer.SubConn](),
|
||||||
|
scStates: make(map[balancer.SubConn]connectivity.State),
|
||||||
|
csEvltr: &balancer.ConnectivityStateEvaluator{},
|
||||||
|
config: bb.config,
|
||||||
|
state: connectivity.Connecting,
|
||||||
|
}
|
||||||
|
// Initialize picker to a picker that always returns
|
||||||
|
// ErrNoSubConnAvailable, because when state of a SubConn changes, we
|
||||||
|
// may call UpdateState with this picker.
|
||||||
|
bal.picker = NewErrPicker(balancer.ErrNoSubConnAvailable)
|
||||||
|
return bal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb *baseBuilder) Name() string {
|
||||||
|
return bb.name
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseBalancer struct {
|
||||||
|
cc balancer.ClientConn
|
||||||
|
pickerBuilder PickerBuilder
|
||||||
|
|
||||||
|
csEvltr *balancer.ConnectivityStateEvaluator
|
||||||
|
state connectivity.State
|
||||||
|
|
||||||
|
subConns *resolver.AddressMapV2[balancer.SubConn]
|
||||||
|
scStates map[balancer.SubConn]connectivity.State
|
||||||
|
picker balancer.Picker
|
||||||
|
config Config
|
||||||
|
|
||||||
|
resolverErr error // the last error reported by the resolver; cleared on successful resolution
|
||||||
|
connErr error // the last connection error; cleared upon leaving TransientFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *baseBalancer) ResolverError(err error) {
|
||||||
|
b.resolverErr = err
|
||||||
|
if b.subConns.Len() == 0 {
|
||||||
|
b.state = connectivity.TransientFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.state != connectivity.TransientFailure {
|
||||||
|
// The picker will not change since the balancer does not currently
|
||||||
|
// report an error.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.regeneratePicker()
|
||||||
|
b.cc.UpdateState(balancer.State{
|
||||||
|
ConnectivityState: b.state,
|
||||||
|
Picker: b.picker,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
|
||||||
|
// TODO: handle s.ResolverState.ServiceConfig?
|
||||||
|
if logger.V(2) {
|
||||||
|
logger.Info("base.baseBalancer: got new ClientConn state: ", s)
|
||||||
|
}
|
||||||
|
// Successful resolution; clear resolver error and ensure we return nil.
|
||||||
|
b.resolverErr = nil
|
||||||
|
// addrsSet is the set converted from addrs, it's used for quick lookup of an address.
|
||||||
|
addrsSet := resolver.NewAddressMapV2[any]()
|
||||||
|
for _, a := range s.ResolverState.Addresses {
|
||||||
|
addrsSet.Set(a, nil)
|
||||||
|
if _, ok := b.subConns.Get(a); !ok {
|
||||||
|
// a is a new address (not existing in b.subConns).
|
||||||
|
var sc balancer.SubConn
|
||||||
|
opts := balancer.NewSubConnOptions{
|
||||||
|
HealthCheckEnabled: b.config.HealthCheck,
|
||||||
|
StateListener: func(scs balancer.SubConnState) { b.updateSubConnState(sc, scs) },
|
||||||
|
}
|
||||||
|
sc, err := b.cc.NewSubConn([]resolver.Address{a}, opts)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("base.baseBalancer: failed to create new SubConn: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b.subConns.Set(a, sc)
|
||||||
|
b.scStates[sc] = connectivity.Idle
|
||||||
|
b.csEvltr.RecordTransition(connectivity.Shutdown, connectivity.Idle)
|
||||||
|
sc.Connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, a := range b.subConns.Keys() {
|
||||||
|
sc, _ := b.subConns.Get(a)
|
||||||
|
// a was removed by resolver.
|
||||||
|
if _, ok := addrsSet.Get(a); !ok {
|
||||||
|
sc.Shutdown()
|
||||||
|
b.subConns.Delete(a)
|
||||||
|
// Keep the state of this sc in b.scStates until sc's state becomes Shutdown.
|
||||||
|
// The entry will be deleted in updateSubConnState.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If resolver state contains no addresses, return an error so ClientConn
|
||||||
|
// will trigger re-resolve. Also records this as a resolver error, so when
|
||||||
|
// the overall state turns transient failure, the error message will have
|
||||||
|
// the zero address information.
|
||||||
|
if len(s.ResolverState.Addresses) == 0 {
|
||||||
|
b.ResolverError(errors.New("produced zero addresses"))
|
||||||
|
return balancer.ErrBadResolverState
|
||||||
|
}
|
||||||
|
|
||||||
|
b.regeneratePicker()
|
||||||
|
b.cc.UpdateState(balancer.State{ConnectivityState: b.state, Picker: b.picker})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeErrors builds an error from the last connection error and the last
|
||||||
|
// resolver error. Must only be called if b.state is TransientFailure.
|
||||||
|
func (b *baseBalancer) mergeErrors() error {
|
||||||
|
// connErr must always be non-nil unless there are no SubConns, in which
|
||||||
|
// case resolverErr must be non-nil.
|
||||||
|
if b.connErr == nil {
|
||||||
|
return fmt.Errorf("last resolver error: %v", b.resolverErr)
|
||||||
|
}
|
||||||
|
if b.resolverErr == nil {
|
||||||
|
return fmt.Errorf("last connection error: %v", b.connErr)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("last connection error: %v; last resolver error: %v", b.connErr, b.resolverErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// regeneratePicker takes a snapshot of the balancer, and generates a picker
|
||||||
|
// from it. The picker is
|
||||||
|
// - errPicker if the balancer is in TransientFailure,
|
||||||
|
// - built by the pickerBuilder with all READY SubConns otherwise.
|
||||||
|
func (b *baseBalancer) regeneratePicker() {
|
||||||
|
if b.state == connectivity.TransientFailure {
|
||||||
|
b.picker = NewErrPicker(b.mergeErrors())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
readySCs := make(map[balancer.SubConn]SubConnInfo)
|
||||||
|
|
||||||
|
// Filter out all ready SCs from full subConn map.
|
||||||
|
for _, addr := range b.subConns.Keys() {
|
||||||
|
sc, _ := b.subConns.Get(addr)
|
||||||
|
if st, ok := b.scStates[sc]; ok && st == connectivity.Ready {
|
||||||
|
readySCs[sc] = SubConnInfo{Address: addr}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.picker = b.pickerBuilder.Build(PickerBuildInfo{ReadySCs: readySCs})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSubConnState is a nop because a StateListener is always set in NewSubConn.
|
||||||
|
func (b *baseBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
|
||||||
|
logger.Errorf("base.baseBalancer: UpdateSubConnState(%v, %+v) called unexpectedly", sc, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *baseBalancer) updateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
|
||||||
|
s := state.ConnectivityState
|
||||||
|
if logger.V(2) {
|
||||||
|
logger.Infof("base.baseBalancer: handle SubConn state change: %p, %v", sc, s)
|
||||||
|
}
|
||||||
|
oldS, ok := b.scStates[sc]
|
||||||
|
if !ok {
|
||||||
|
if logger.V(2) {
|
||||||
|
logger.Infof("base.baseBalancer: got state changes for an unknown SubConn: %p, %v", sc, s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if oldS == connectivity.TransientFailure &&
|
||||||
|
(s == connectivity.Connecting || s == connectivity.Idle) {
|
||||||
|
// Once a subconn enters TRANSIENT_FAILURE, ignore subsequent IDLE or
|
||||||
|
// CONNECTING transitions to prevent the aggregated state from being
|
||||||
|
// always CONNECTING when many backends exist but are all down.
|
||||||
|
if s == connectivity.Idle {
|
||||||
|
sc.Connect()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.scStates[sc] = s
|
||||||
|
switch s {
|
||||||
|
case connectivity.Idle:
|
||||||
|
sc.Connect()
|
||||||
|
case connectivity.Shutdown:
|
||||||
|
// When an address was removed by resolver, b called Shutdown but kept
|
||||||
|
// the sc's state in scStates. Remove state for this sc here.
|
||||||
|
delete(b.scStates, sc)
|
||||||
|
case connectivity.TransientFailure:
|
||||||
|
// Save error to be reported via picker.
|
||||||
|
b.connErr = state.ConnectionError
|
||||||
|
}
|
||||||
|
|
||||||
|
b.state = b.csEvltr.RecordTransition(oldS, s)
|
||||||
|
|
||||||
|
// Regenerate picker when one of the following happens:
|
||||||
|
// - this sc entered or left ready
|
||||||
|
// - the aggregated state of balancer is TransientFailure
|
||||||
|
// (may need to update error message)
|
||||||
|
if (s == connectivity.Ready) != (oldS == connectivity.Ready) ||
|
||||||
|
b.state == connectivity.TransientFailure {
|
||||||
|
b.regeneratePicker()
|
||||||
|
}
|
||||||
|
b.cc.UpdateState(balancer.State{ConnectivityState: b.state, Picker: b.picker})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is a nop because base balancer doesn't have internal state to clean up,
|
||||||
|
// and it doesn't need to call Shutdown for the SubConns.
|
||||||
|
func (b *baseBalancer) Close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitIdle is a nop because the base balancer attempts to stay connected to
|
||||||
|
// all SubConns at all times.
|
||||||
|
func (b *baseBalancer) ExitIdle() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrPicker returns a Picker that always returns err on Pick().
|
||||||
|
func NewErrPicker(err error) balancer.Picker {
|
||||||
|
return &errPicker{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrPickerV2 is temporarily defined for backward compatibility reasons.
|
||||||
|
//
|
||||||
|
// Deprecated: use NewErrPicker instead.
|
||||||
|
var NewErrPickerV2 = NewErrPicker
|
||||||
|
|
||||||
|
type errPicker struct {
|
||||||
|
err error // Pick() always returns this err.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *errPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
|
||||||
|
return balancer.PickResult{}, p.err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 base defines a balancer base that can be used to build balancers with
|
||||||
|
// different picking algorithms.
|
||||||
|
//
|
||||||
|
// The base balancer creates a new SubConn for each resolved address. The
|
||||||
|
// provided picker will only be notified about READY SubConns.
|
||||||
|
//
|
||||||
|
// This package is the base of round_robin balancer, its purpose is to be used
|
||||||
|
// to build round_robin like balancers with complex picking algorithms.
|
||||||
|
// Balancers with more complicated logic should try to implement a balancer
|
||||||
|
// builder from scratch.
|
||||||
|
//
|
||||||
|
// All APIs in this package are experimental.
|
||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PickerBuilder creates balancer.Picker.
|
||||||
|
type PickerBuilder interface {
|
||||||
|
// Build returns a picker that will be used by gRPC to pick a SubConn.
|
||||||
|
Build(info PickerBuildInfo) balancer.Picker
|
||||||
|
}
|
||||||
|
|
||||||
|
// PickerBuildInfo contains information needed by the picker builder to
|
||||||
|
// construct a picker.
|
||||||
|
type PickerBuildInfo struct {
|
||||||
|
// ReadySCs is a map from all ready SubConns to the Addresses used to
|
||||||
|
// create them.
|
||||||
|
ReadySCs map[balancer.SubConn]SubConnInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubConnInfo contains information about a SubConn created by the base
|
||||||
|
// balancer.
|
||||||
|
type SubConnInfo struct {
|
||||||
|
Address resolver.Address // the address used to create this SubConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config contains the config info about the base balancer builder.
|
||||||
|
type Config struct {
|
||||||
|
// HealthCheck indicates whether health checking should be enabled for this specific balancer.
|
||||||
|
HealthCheck bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBalancerBuilder returns a base balancer builder configured by the provided config.
|
||||||
|
func NewBalancerBuilder(name string, pb PickerBuilder, config Config) balancer.Builder {
|
||||||
|
return &baseBuilder{
|
||||||
|
name: name,
|
||||||
|
pickerBuilder: pb,
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2022 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 balancer
|
||||||
|
|
||||||
|
import "google.golang.org/grpc/connectivity"
|
||||||
|
|
||||||
|
// ConnectivityStateEvaluator takes the connectivity states of multiple SubConns
|
||||||
|
// and returns one aggregated connectivity state.
|
||||||
|
//
|
||||||
|
// It's not thread safe.
|
||||||
|
type ConnectivityStateEvaluator struct {
|
||||||
|
numReady uint64 // Number of addrConns in ready state.
|
||||||
|
numConnecting uint64 // Number of addrConns in connecting state.
|
||||||
|
numTransientFailure uint64 // Number of addrConns in transient failure state.
|
||||||
|
numIdle uint64 // Number of addrConns in idle state.
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordTransition records state change happening in subConn and based on that
|
||||||
|
// it evaluates what aggregated state should be.
|
||||||
|
//
|
||||||
|
// - If at least one SubConn in Ready, the aggregated state is Ready;
|
||||||
|
// - Else if at least one SubConn in Connecting, the aggregated state is Connecting;
|
||||||
|
// - Else if at least one SubConn is Idle, the aggregated state is Idle;
|
||||||
|
// - Else if at least one SubConn is TransientFailure (or there are no SubConns), the aggregated state is Transient Failure.
|
||||||
|
//
|
||||||
|
// Shutdown is not considered.
|
||||||
|
func (cse *ConnectivityStateEvaluator) RecordTransition(oldState, newState connectivity.State) connectivity.State {
|
||||||
|
// Update counters.
|
||||||
|
for idx, state := range []connectivity.State{oldState, newState} {
|
||||||
|
updateVal := 2*uint64(idx) - 1 // -1 for oldState and +1 for new.
|
||||||
|
switch state {
|
||||||
|
case connectivity.Ready:
|
||||||
|
cse.numReady += updateVal
|
||||||
|
case connectivity.Connecting:
|
||||||
|
cse.numConnecting += updateVal
|
||||||
|
case connectivity.TransientFailure:
|
||||||
|
cse.numTransientFailure += updateVal
|
||||||
|
case connectivity.Idle:
|
||||||
|
cse.numIdle += updateVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cse.CurrentState()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentState returns the current aggregate conn state by evaluating the counters
|
||||||
|
func (cse *ConnectivityStateEvaluator) CurrentState() connectivity.State {
|
||||||
|
// Evaluate.
|
||||||
|
if cse.numReady > 0 {
|
||||||
|
return connectivity.Ready
|
||||||
|
}
|
||||||
|
if cse.numConnecting > 0 {
|
||||||
|
return connectivity.Connecting
|
||||||
|
}
|
||||||
|
if cse.numIdle > 0 {
|
||||||
|
return connectivity.Idle
|
||||||
|
}
|
||||||
|
return connectivity.TransientFailure
|
||||||
|
}
|
||||||
389
vendor/google.golang.org/grpc/balancer/endpointsharding/endpointsharding.go
generated
vendored
Normal file
389
vendor/google.golang.org/grpc/balancer/endpointsharding/endpointsharding.go
generated
vendored
Normal file
|
|
@ -0,0 +1,389 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 endpointsharding implements a load balancing policy that manages
|
||||||
|
// homogeneous child policies each owning a single endpoint.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This package is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
package endpointsharding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
rand "math/rand/v2"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/balancer/base"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
var randIntN = rand.IntN
|
||||||
|
|
||||||
|
// ChildState is the balancer state of a child along with the endpoint which
|
||||||
|
// identifies the child balancer.
|
||||||
|
type ChildState struct {
|
||||||
|
Endpoint resolver.Endpoint
|
||||||
|
State balancer.State
|
||||||
|
|
||||||
|
// Balancer exposes only the ExitIdler interface of the child LB policy.
|
||||||
|
// Other methods of the child policy are called only by endpointsharding.
|
||||||
|
Balancer ExitIdler
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitIdler provides access to only the ExitIdle method of the child balancer.
|
||||||
|
type ExitIdler interface {
|
||||||
|
// ExitIdle instructs the LB policy to reconnect to backends / exit the
|
||||||
|
// IDLE state, if appropriate and possible. Note that SubConns that enter
|
||||||
|
// the IDLE state will not reconnect until SubConn.Connect is called.
|
||||||
|
ExitIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options are the options to configure the behaviour of the
|
||||||
|
// endpointsharding balancer.
|
||||||
|
type Options struct {
|
||||||
|
// DisableAutoReconnect allows the balancer to keep child balancer in the
|
||||||
|
// IDLE state until they are explicitly triggered to exit using the
|
||||||
|
// ChildState obtained from the endpointsharding picker. When set to false,
|
||||||
|
// the endpointsharding balancer will automatically call ExitIdle on child
|
||||||
|
// connections that report IDLE.
|
||||||
|
DisableAutoReconnect bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildBuilderFunc creates a new balancer with the ClientConn. It has the same
|
||||||
|
// type as the balancer.Builder.Build method.
|
||||||
|
type ChildBuilderFunc func(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer
|
||||||
|
|
||||||
|
// NewBalancer returns a load balancing policy that manages homogeneous child
|
||||||
|
// policies each owning a single endpoint. The endpointsharding balancer
|
||||||
|
// forwards the LoadBalancingConfig in ClientConn state updates to its children.
|
||||||
|
func NewBalancer(cc balancer.ClientConn, opts balancer.BuildOptions, childBuilder ChildBuilderFunc, esOpts Options) balancer.Balancer {
|
||||||
|
es := &endpointSharding{
|
||||||
|
cc: cc,
|
||||||
|
bOpts: opts,
|
||||||
|
esOpts: esOpts,
|
||||||
|
childBuilder: childBuilder,
|
||||||
|
}
|
||||||
|
es.children.Store(resolver.NewEndpointMap[*balancerWrapper]())
|
||||||
|
return es
|
||||||
|
}
|
||||||
|
|
||||||
|
// endpointSharding is a balancer that wraps child balancers. It creates a child
|
||||||
|
// balancer with child config for every unique Endpoint received. It updates the
|
||||||
|
// child states on any update from parent or child.
|
||||||
|
type endpointSharding struct {
|
||||||
|
cc balancer.ClientConn
|
||||||
|
bOpts balancer.BuildOptions
|
||||||
|
esOpts Options
|
||||||
|
childBuilder ChildBuilderFunc
|
||||||
|
|
||||||
|
// childMu synchronizes calls to any single child. It must be held for all
|
||||||
|
// calls into a child. To avoid deadlocks, do not acquire childMu while
|
||||||
|
// holding mu.
|
||||||
|
childMu sync.Mutex
|
||||||
|
children atomic.Pointer[resolver.EndpointMap[*balancerWrapper]]
|
||||||
|
|
||||||
|
// inhibitChildUpdates is set during UpdateClientConnState/ResolverError
|
||||||
|
// calls (calls to children will each produce an update, only want one
|
||||||
|
// update).
|
||||||
|
inhibitChildUpdates atomic.Bool
|
||||||
|
|
||||||
|
// mu synchronizes access to the state stored in balancerWrappers in the
|
||||||
|
// children field. mu must not be held during calls into a child since
|
||||||
|
// synchronous calls back from the child may require taking mu, causing a
|
||||||
|
// deadlock. To avoid deadlocks, do not acquire childMu while holding mu.
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotateEndpoints returns a slice of all the input endpoints rotated a random
|
||||||
|
// amount.
|
||||||
|
func rotateEndpoints(es []resolver.Endpoint) []resolver.Endpoint {
|
||||||
|
les := len(es)
|
||||||
|
if les == 0 {
|
||||||
|
return es
|
||||||
|
}
|
||||||
|
r := randIntN(les)
|
||||||
|
// Make a copy to avoid mutating data beyond the end of es.
|
||||||
|
ret := make([]resolver.Endpoint, les)
|
||||||
|
copy(ret, es[r:])
|
||||||
|
copy(ret[les-r:], es[:r])
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateClientConnState creates a child for new endpoints and deletes children
|
||||||
|
// for endpoints that are no longer present. It also updates all the children,
|
||||||
|
// and sends a single synchronous update of the childrens' aggregated state at
|
||||||
|
// the end of the UpdateClientConnState operation. If any endpoint has no
|
||||||
|
// addresses it will ignore that endpoint. Otherwise, returns first error found
|
||||||
|
// from a child, but fully processes the new update.
|
||||||
|
func (es *endpointSharding) UpdateClientConnState(state balancer.ClientConnState) error {
|
||||||
|
es.childMu.Lock()
|
||||||
|
defer es.childMu.Unlock()
|
||||||
|
|
||||||
|
es.inhibitChildUpdates.Store(true)
|
||||||
|
defer func() {
|
||||||
|
es.inhibitChildUpdates.Store(false)
|
||||||
|
es.updateState()
|
||||||
|
}()
|
||||||
|
var ret error
|
||||||
|
|
||||||
|
children := es.children.Load()
|
||||||
|
newChildren := resolver.NewEndpointMap[*balancerWrapper]()
|
||||||
|
|
||||||
|
// Update/Create new children.
|
||||||
|
for _, endpoint := range rotateEndpoints(state.ResolverState.Endpoints) {
|
||||||
|
if _, ok := newChildren.Get(endpoint); ok {
|
||||||
|
// Endpoint child was already created, continue to avoid duplicate
|
||||||
|
// update.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
childBalancer, ok := children.Get(endpoint)
|
||||||
|
if ok {
|
||||||
|
// Endpoint attributes may have changed, update the stored endpoint.
|
||||||
|
es.mu.Lock()
|
||||||
|
childBalancer.childState.Endpoint = endpoint
|
||||||
|
es.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
childBalancer = &balancerWrapper{
|
||||||
|
childState: ChildState{Endpoint: endpoint},
|
||||||
|
ClientConn: es.cc,
|
||||||
|
es: es,
|
||||||
|
}
|
||||||
|
childBalancer.childState.Balancer = childBalancer
|
||||||
|
childBalancer.child = es.childBuilder(childBalancer, es.bOpts)
|
||||||
|
}
|
||||||
|
newChildren.Set(endpoint, childBalancer)
|
||||||
|
if err := childBalancer.updateClientConnStateLocked(balancer.ClientConnState{
|
||||||
|
BalancerConfig: state.BalancerConfig,
|
||||||
|
ResolverState: resolver.State{
|
||||||
|
Endpoints: []resolver.Endpoint{endpoint},
|
||||||
|
Attributes: state.ResolverState.Attributes,
|
||||||
|
},
|
||||||
|
}); err != nil && ret == nil {
|
||||||
|
// Return first error found, and always commit full processing of
|
||||||
|
// updating children. If desired to process more specific errors
|
||||||
|
// across all endpoints, caller should make these specific
|
||||||
|
// validations, this is a current limitation for simplicity sake.
|
||||||
|
ret = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Delete old children that are no longer present.
|
||||||
|
for _, e := range children.Keys() {
|
||||||
|
child, _ := children.Get(e)
|
||||||
|
if _, ok := newChildren.Get(e); !ok {
|
||||||
|
child.closeLocked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
es.children.Store(newChildren)
|
||||||
|
if newChildren.Len() == 0 {
|
||||||
|
return balancer.ErrBadResolverState
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolverError forwards the resolver error to all of the endpointSharding's
|
||||||
|
// children and sends a single synchronous update of the childStates at the end
|
||||||
|
// of the ResolverError operation.
|
||||||
|
func (es *endpointSharding) ResolverError(err error) {
|
||||||
|
es.childMu.Lock()
|
||||||
|
defer es.childMu.Unlock()
|
||||||
|
es.inhibitChildUpdates.Store(true)
|
||||||
|
defer func() {
|
||||||
|
es.inhibitChildUpdates.Store(false)
|
||||||
|
es.updateState()
|
||||||
|
}()
|
||||||
|
children := es.children.Load()
|
||||||
|
for _, child := range children.Values() {
|
||||||
|
child.resolverErrorLocked(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *endpointSharding) UpdateSubConnState(balancer.SubConn, balancer.SubConnState) {
|
||||||
|
// UpdateSubConnState is deprecated.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *endpointSharding) Close() {
|
||||||
|
es.childMu.Lock()
|
||||||
|
defer es.childMu.Unlock()
|
||||||
|
children := es.children.Load()
|
||||||
|
for _, child := range children.Values() {
|
||||||
|
child.closeLocked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *endpointSharding) ExitIdle() {
|
||||||
|
es.childMu.Lock()
|
||||||
|
defer es.childMu.Unlock()
|
||||||
|
for _, bw := range es.children.Load().Values() {
|
||||||
|
if !bw.isClosed {
|
||||||
|
bw.child.ExitIdle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateState updates this component's state. It sends the aggregated state,
|
||||||
|
// and a picker with round robin behavior with all the child states present if
|
||||||
|
// needed.
|
||||||
|
func (es *endpointSharding) updateState() {
|
||||||
|
if es.inhibitChildUpdates.Load() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var readyPickers, connectingPickers, idlePickers, transientFailurePickers []balancer.Picker
|
||||||
|
|
||||||
|
es.mu.Lock()
|
||||||
|
defer es.mu.Unlock()
|
||||||
|
|
||||||
|
children := es.children.Load()
|
||||||
|
childStates := make([]ChildState, 0, children.Len())
|
||||||
|
|
||||||
|
for _, child := range children.Values() {
|
||||||
|
childState := child.childState
|
||||||
|
childStates = append(childStates, childState)
|
||||||
|
childPicker := childState.State.Picker
|
||||||
|
switch childState.State.ConnectivityState {
|
||||||
|
case connectivity.Ready:
|
||||||
|
readyPickers = append(readyPickers, childPicker)
|
||||||
|
case connectivity.Connecting:
|
||||||
|
connectingPickers = append(connectingPickers, childPicker)
|
||||||
|
case connectivity.Idle:
|
||||||
|
idlePickers = append(idlePickers, childPicker)
|
||||||
|
case connectivity.TransientFailure:
|
||||||
|
transientFailurePickers = append(transientFailurePickers, childPicker)
|
||||||
|
// connectivity.Shutdown shouldn't appear.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the round robin picker based off the aggregated state. Whatever
|
||||||
|
// the aggregated state, use the pickers present that are currently in that
|
||||||
|
// state only.
|
||||||
|
var aggState connectivity.State
|
||||||
|
var pickers []balancer.Picker
|
||||||
|
if len(readyPickers) >= 1 {
|
||||||
|
aggState = connectivity.Ready
|
||||||
|
pickers = readyPickers
|
||||||
|
} else if len(connectingPickers) >= 1 {
|
||||||
|
aggState = connectivity.Connecting
|
||||||
|
pickers = connectingPickers
|
||||||
|
} else if len(idlePickers) >= 1 {
|
||||||
|
aggState = connectivity.Idle
|
||||||
|
pickers = idlePickers
|
||||||
|
} else if len(transientFailurePickers) >= 1 {
|
||||||
|
aggState = connectivity.TransientFailure
|
||||||
|
pickers = transientFailurePickers
|
||||||
|
} else {
|
||||||
|
aggState = connectivity.TransientFailure
|
||||||
|
pickers = []balancer.Picker{base.NewErrPicker(errors.New("no children to pick from"))}
|
||||||
|
} // No children (resolver error before valid update).
|
||||||
|
p := &pickerWithChildStates{
|
||||||
|
pickers: pickers,
|
||||||
|
childStates: childStates,
|
||||||
|
next: uint32(randIntN(len(pickers))),
|
||||||
|
}
|
||||||
|
es.cc.UpdateState(balancer.State{
|
||||||
|
ConnectivityState: aggState,
|
||||||
|
Picker: p,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// pickerWithChildStates delegates to the pickers it holds in a round robin
|
||||||
|
// fashion. It also contains the childStates of all the endpointSharding's
|
||||||
|
// children.
|
||||||
|
type pickerWithChildStates struct {
|
||||||
|
pickers []balancer.Picker
|
||||||
|
childStates []ChildState
|
||||||
|
next uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pickerWithChildStates) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
|
||||||
|
nextIndex := atomic.AddUint32(&p.next, 1)
|
||||||
|
picker := p.pickers[nextIndex%uint32(len(p.pickers))]
|
||||||
|
return picker.Pick(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildStatesFromPicker returns the state of all the children managed by the
|
||||||
|
// endpoint sharding balancer that created this picker.
|
||||||
|
func ChildStatesFromPicker(picker balancer.Picker) []ChildState {
|
||||||
|
p, ok := picker.(*pickerWithChildStates)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return p.childStates
|
||||||
|
}
|
||||||
|
|
||||||
|
// balancerWrapper is a wrapper of a balancer. It ID's a child balancer by
|
||||||
|
// endpoint, and persists recent child balancer state.
|
||||||
|
type balancerWrapper struct {
|
||||||
|
// The following fields are initialized at build time and read-only after
|
||||||
|
// that and therefore do not need to be guarded by a mutex.
|
||||||
|
|
||||||
|
// child contains the wrapped balancer. Access its methods only through
|
||||||
|
// methods on balancerWrapper to ensure proper synchronization
|
||||||
|
child balancer.Balancer
|
||||||
|
balancer.ClientConn // embed to intercept UpdateState, doesn't deal with SubConns
|
||||||
|
|
||||||
|
es *endpointSharding
|
||||||
|
|
||||||
|
// Access to the following fields is guarded by es.mu.
|
||||||
|
|
||||||
|
childState ChildState
|
||||||
|
isClosed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *balancerWrapper) UpdateState(state balancer.State) {
|
||||||
|
bw.es.mu.Lock()
|
||||||
|
bw.childState.State = state
|
||||||
|
bw.es.mu.Unlock()
|
||||||
|
if state.ConnectivityState == connectivity.Idle && !bw.es.esOpts.DisableAutoReconnect {
|
||||||
|
bw.ExitIdle()
|
||||||
|
}
|
||||||
|
bw.es.updateState()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitIdle pings an IDLE child balancer to exit idle in a new goroutine to
|
||||||
|
// avoid deadlocks due to synchronous balancer state updates.
|
||||||
|
func (bw *balancerWrapper) ExitIdle() {
|
||||||
|
go func() {
|
||||||
|
bw.es.childMu.Lock()
|
||||||
|
if !bw.isClosed {
|
||||||
|
bw.child.ExitIdle()
|
||||||
|
}
|
||||||
|
bw.es.childMu.Unlock()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateClientConnStateLocked delivers the ClientConnState to the child
|
||||||
|
// balancer. Callers must hold the child mutex of the parent endpointsharding
|
||||||
|
// balancer.
|
||||||
|
func (bw *balancerWrapper) updateClientConnStateLocked(ccs balancer.ClientConnState) error {
|
||||||
|
return bw.child.UpdateClientConnState(ccs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeLocked closes the child balancer. Callers must hold the child mutext of
|
||||||
|
// the parent endpointsharding balancer.
|
||||||
|
func (bw *balancerWrapper) closeLocked() {
|
||||||
|
bw.child.Close()
|
||||||
|
bw.isClosed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *balancerWrapper) resolverErrorLocked(err error) {
|
||||||
|
bw.child.ResolverError(err)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 state declares grpclb types to be set by resolvers wishing to pass
|
||||||
|
// information to grpclb via resolver.State Attributes.
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// keyType is the key to use for storing State in Attributes.
|
||||||
|
type keyType string
|
||||||
|
|
||||||
|
const key = keyType("grpc.grpclb.state")
|
||||||
|
|
||||||
|
// State contains gRPCLB-relevant data passed from the name resolver.
|
||||||
|
type State struct {
|
||||||
|
// BalancerAddresses contains the remote load balancer address(es). If
|
||||||
|
// set, overrides any resolver-provided addresses with Type of GRPCLB.
|
||||||
|
BalancerAddresses []resolver.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set returns a copy of the provided state with attributes containing s. s's
|
||||||
|
// data should not be mutated after calling Set.
|
||||||
|
func Set(state resolver.State, s *State) resolver.State {
|
||||||
|
state.Attributes = state.Attributes.WithValue(key, s)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the grpclb State in the resolver.State, or nil if not present.
|
||||||
|
// The returned data should not be mutated.
|
||||||
|
func Get(state resolver.State) *State {
|
||||||
|
s, _ := state.Attributes.Value(key).(*State)
|
||||||
|
return s
|
||||||
|
}
|
||||||
37
vendor/google.golang.org/grpc/balancer/pickfirst/internal/internal.go
generated
vendored
Normal file
37
vendor/google.golang.org/grpc/balancer/pickfirst/internal/internal.go
generated
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 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 internal contains code internal to the pickfirst package.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
rand "math/rand/v2"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// RandShuffle pseudo-randomizes the order of addresses.
|
||||||
|
RandShuffle = rand.Shuffle
|
||||||
|
// RandFloat64 returns, as a float64, a pseudo-random number in [0.0,1.0).
|
||||||
|
RandFloat64 = rand.Float64
|
||||||
|
// TimeAfterFunc allows mocking the timer for testing connection delay
|
||||||
|
// related functionality.
|
||||||
|
TimeAfterFunc = func(d time.Duration, f func()) func() {
|
||||||
|
timer := time.AfterFunc(d, f)
|
||||||
|
return func() { timer.Stop() }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,961 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 pickfirst contains the pick_first load balancing policy which
|
||||||
|
// is the universal leaf policy.
|
||||||
|
package pickfirst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/balancer/pickfirst/internal"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
expstats "google.golang.org/grpc/experimental/stats"
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
"google.golang.org/grpc/internal/balancer/weight"
|
||||||
|
"google.golang.org/grpc/internal/envconfig"
|
||||||
|
internalgrpclog "google.golang.org/grpc/internal/grpclog"
|
||||||
|
"google.golang.org/grpc/internal/pretty"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
"google.golang.org/grpc/serviceconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
balancer.Register(pickfirstBuilder{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name is the name of the pick_first balancer.
|
||||||
|
const Name = "pick_first"
|
||||||
|
|
||||||
|
// enableHealthListenerKeyType is a unique key type used in resolver
|
||||||
|
// attributes to indicate whether the health listener usage is enabled.
|
||||||
|
type enableHealthListenerKeyType struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger = grpclog.Component("pick-first-leaf-lb")
|
||||||
|
disconnectionsMetric = expstats.RegisterInt64Count(expstats.MetricDescriptor{
|
||||||
|
Name: "grpc.lb.pick_first.disconnections",
|
||||||
|
Description: "EXPERIMENTAL. Number of times the selected subchannel becomes disconnected.",
|
||||||
|
Unit: "{disconnection}",
|
||||||
|
Labels: []string{"grpc.target"},
|
||||||
|
Default: false,
|
||||||
|
})
|
||||||
|
connectionAttemptsSucceededMetric = expstats.RegisterInt64Count(expstats.MetricDescriptor{
|
||||||
|
Name: "grpc.lb.pick_first.connection_attempts_succeeded",
|
||||||
|
Description: "EXPERIMENTAL. Number of successful connection attempts.",
|
||||||
|
Unit: "{attempt}",
|
||||||
|
Labels: []string{"grpc.target"},
|
||||||
|
Default: false,
|
||||||
|
})
|
||||||
|
connectionAttemptsFailedMetric = expstats.RegisterInt64Count(expstats.MetricDescriptor{
|
||||||
|
Name: "grpc.lb.pick_first.connection_attempts_failed",
|
||||||
|
Description: "EXPERIMENTAL. Number of failed connection attempts.",
|
||||||
|
Unit: "{attempt}",
|
||||||
|
Labels: []string{"grpc.target"},
|
||||||
|
Default: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TODO: change to pick-first when this becomes the default pick_first policy.
|
||||||
|
logPrefix = "[pick-first-leaf-lb %p] "
|
||||||
|
// connectionDelayInterval is the time to wait for during the happy eyeballs
|
||||||
|
// pass before starting the next connection attempt.
|
||||||
|
connectionDelayInterval = 250 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
type ipAddrFamily int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ipAddrFamilyUnknown represents strings that can't be parsed as an IP
|
||||||
|
// address.
|
||||||
|
ipAddrFamilyUnknown ipAddrFamily = iota
|
||||||
|
ipAddrFamilyV4
|
||||||
|
ipAddrFamilyV6
|
||||||
|
)
|
||||||
|
|
||||||
|
type pickfirstBuilder struct{}
|
||||||
|
|
||||||
|
func (pickfirstBuilder) Build(cc balancer.ClientConn, bo balancer.BuildOptions) balancer.Balancer {
|
||||||
|
b := &pickfirstBalancer{
|
||||||
|
cc: cc,
|
||||||
|
target: bo.Target.String(),
|
||||||
|
metricsRecorder: cc.MetricsRecorder(),
|
||||||
|
|
||||||
|
subConns: resolver.NewAddressMapV2[*scData](),
|
||||||
|
state: connectivity.Connecting,
|
||||||
|
cancelConnectionTimer: func() {},
|
||||||
|
}
|
||||||
|
b.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(logPrefix, b))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b pickfirstBuilder) Name() string {
|
||||||
|
return Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pickfirstBuilder) ParseConfig(js json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
||||||
|
var cfg pfConfig
|
||||||
|
if err := json.Unmarshal(js, &cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("pickfirst: unable to unmarshal LB policy config: %s, error: %v", string(js), err)
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableHealthListener updates the state to configure pickfirst for using a
|
||||||
|
// generic health listener.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a later
|
||||||
|
// release.
|
||||||
|
func EnableHealthListener(state resolver.State) resolver.State {
|
||||||
|
state.Attributes = state.Attributes.WithValue(enableHealthListenerKeyType{}, true)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
type pfConfig struct {
|
||||||
|
serviceconfig.LoadBalancingConfig `json:"-"`
|
||||||
|
|
||||||
|
// If set to true, instructs the LB policy to shuffle the order of the list
|
||||||
|
// of endpoints received from the name resolver before attempting to
|
||||||
|
// connect to them.
|
||||||
|
ShuffleAddressList bool `json:"shuffleAddressList"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// scData keeps track of the current state of the subConn.
|
||||||
|
// It is not safe for concurrent access.
|
||||||
|
type scData struct {
|
||||||
|
// The following fields are initialized at build time and read-only after
|
||||||
|
// that.
|
||||||
|
subConn balancer.SubConn
|
||||||
|
addr resolver.Address
|
||||||
|
|
||||||
|
rawConnectivityState connectivity.State
|
||||||
|
// The effective connectivity state based on raw connectivity, health state
|
||||||
|
// and after following sticky TransientFailure behaviour defined in A62.
|
||||||
|
effectiveState connectivity.State
|
||||||
|
lastErr error
|
||||||
|
connectionFailedInFirstPass bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *pickfirstBalancer) newSCData(addr resolver.Address) (*scData, error) {
|
||||||
|
sd := &scData{
|
||||||
|
rawConnectivityState: connectivity.Idle,
|
||||||
|
effectiveState: connectivity.Idle,
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
|
sc, err := b.cc.NewSubConn([]resolver.Address{addr}, balancer.NewSubConnOptions{
|
||||||
|
StateListener: func(state balancer.SubConnState) {
|
||||||
|
b.updateSubConnState(sd, state)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sd.subConn = sc
|
||||||
|
return sd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type pickfirstBalancer struct {
|
||||||
|
// The following fields are initialized at build time and read-only after
|
||||||
|
// that and therefore do not need to be guarded by a mutex.
|
||||||
|
logger *internalgrpclog.PrefixLogger
|
||||||
|
cc balancer.ClientConn
|
||||||
|
target string
|
||||||
|
metricsRecorder expstats.MetricsRecorder // guaranteed to be non nil
|
||||||
|
|
||||||
|
// The mutex is used to ensure synchronization of updates triggered
|
||||||
|
// from the idle picker and the already serialized resolver,
|
||||||
|
// SubConn state updates.
|
||||||
|
mu sync.Mutex
|
||||||
|
// State reported to the channel based on SubConn states and resolver
|
||||||
|
// updates.
|
||||||
|
state connectivity.State
|
||||||
|
// scData for active subonns mapped by address.
|
||||||
|
subConns *resolver.AddressMapV2[*scData]
|
||||||
|
addressList addressList
|
||||||
|
firstPass bool
|
||||||
|
numTF int
|
||||||
|
cancelConnectionTimer func()
|
||||||
|
healthCheckingEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolverError is called by the ClientConn when the name resolver produces
|
||||||
|
// an error or when pickfirst determined the resolver update to be invalid.
|
||||||
|
func (b *pickfirstBalancer) ResolverError(err error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
b.resolverErrorLocked(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *pickfirstBalancer) resolverErrorLocked(err error) {
|
||||||
|
if b.logger.V(2) {
|
||||||
|
b.logger.Infof("Received error from the name resolver: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The picker will not change since the balancer does not currently
|
||||||
|
// report an error. If the balancer hasn't received a single good resolver
|
||||||
|
// update yet, transition to TRANSIENT_FAILURE.
|
||||||
|
if b.state != connectivity.TransientFailure && b.addressList.size() > 0 {
|
||||||
|
if b.logger.V(2) {
|
||||||
|
b.logger.Infof("Ignoring resolver error because balancer is using a previous good update.")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.updateBalancerState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.TransientFailure,
|
||||||
|
Picker: &picker{err: fmt.Errorf("name resolver error: %v", err)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState) error {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
b.cancelConnectionTimer()
|
||||||
|
if len(state.ResolverState.Addresses) == 0 && len(state.ResolverState.Endpoints) == 0 {
|
||||||
|
// Cleanup state pertaining to the previous resolver state.
|
||||||
|
// Treat an empty address list like an error by calling b.ResolverError.
|
||||||
|
b.closeSubConnsLocked()
|
||||||
|
b.addressList.updateAddrs(nil)
|
||||||
|
b.resolverErrorLocked(errors.New("produced zero addresses"))
|
||||||
|
return balancer.ErrBadResolverState
|
||||||
|
}
|
||||||
|
b.healthCheckingEnabled = state.ResolverState.Attributes.Value(enableHealthListenerKeyType{}) != nil
|
||||||
|
cfg, ok := state.BalancerConfig.(pfConfig)
|
||||||
|
if state.BalancerConfig != nil && !ok {
|
||||||
|
return fmt.Errorf("pickfirst: received illegal BalancerConfig (type %T): %v: %w", state.BalancerConfig, state.BalancerConfig, balancer.ErrBadResolverState)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.logger.V(2) {
|
||||||
|
b.logger.Infof("Received new config %s, resolver state %s", pretty.ToJSON(cfg), pretty.ToJSON(state.ResolverState))
|
||||||
|
}
|
||||||
|
|
||||||
|
var newAddrs []resolver.Address
|
||||||
|
if endpoints := state.ResolverState.Endpoints; len(endpoints) != 0 {
|
||||||
|
// Perform the optional shuffling described in gRFC A62. The shuffling
|
||||||
|
// will change the order of endpoints but not touch the order of the
|
||||||
|
// addresses within each endpoint. - A61
|
||||||
|
if cfg.ShuffleAddressList {
|
||||||
|
if envconfig.PickFirstWeightedShuffling {
|
||||||
|
type weightedEndpoint struct {
|
||||||
|
endpoint resolver.Endpoint
|
||||||
|
weight float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each endpoint, compute a key as described in A113 and
|
||||||
|
// https://utopia.duth.gr/~pefraimi/research/data/2007EncOfAlg.pdf:
|
||||||
|
var weightedEndpoints []weightedEndpoint
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
u := internal.RandFloat64() // Random number in [0.0, 1.0)
|
||||||
|
weight := weightAttribute(endpoint)
|
||||||
|
weightedEndpoints = append(weightedEndpoints, weightedEndpoint{
|
||||||
|
endpoint: endpoint,
|
||||||
|
weight: math.Pow(u, 1.0/float64(weight)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Sort endpoints by key in descending order and reconstruct the
|
||||||
|
// endpoints slice.
|
||||||
|
slices.SortFunc(weightedEndpoints, func(a, b weightedEndpoint) int {
|
||||||
|
return cmp.Compare(b.weight, a.weight)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Here, and in the "else" block below, we clone the endpoints
|
||||||
|
// slice to avoid mutating the resolver state. Doing the latter
|
||||||
|
// would lead to data races if the caller is accessing the same
|
||||||
|
// slice concurrently.
|
||||||
|
sortedEndpoints := make([]resolver.Endpoint, len(endpoints))
|
||||||
|
for i, we := range weightedEndpoints {
|
||||||
|
sortedEndpoints[i] = we.endpoint
|
||||||
|
}
|
||||||
|
endpoints = sortedEndpoints
|
||||||
|
} else {
|
||||||
|
endpoints = slices.Clone(endpoints)
|
||||||
|
internal.RandShuffle(len(endpoints), func(i, j int) { endpoints[i], endpoints[j] = endpoints[j], endpoints[i] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Flatten the list by concatenating the ordered list of addresses for
|
||||||
|
// each of the endpoints, in order." - A61
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
newAddrs = append(newAddrs, endpoint.Addresses...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Endpoints not set, process addresses until we migrate resolver
|
||||||
|
// emissions fully to Endpoints. The top channel does wrap emitted
|
||||||
|
// addresses with endpoints, however some balancers such as weighted
|
||||||
|
// target do not forward the corresponding correct endpoints down/split
|
||||||
|
// endpoints properly. Once all balancers correctly forward endpoints
|
||||||
|
// down, can delete this else conditional.
|
||||||
|
newAddrs = state.ResolverState.Addresses
|
||||||
|
if cfg.ShuffleAddressList {
|
||||||
|
newAddrs = append([]resolver.Address{}, newAddrs...)
|
||||||
|
internal.RandShuffle(len(newAddrs), func(i, j int) { newAddrs[i], newAddrs[j] = newAddrs[j], newAddrs[i] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an address appears in multiple endpoints or in the same endpoint
|
||||||
|
// multiple times, we keep it only once. We will create only one SubConn
|
||||||
|
// for the address because an AddressMap is used to store SubConns.
|
||||||
|
// Not de-duplicating would result in attempting to connect to the same
|
||||||
|
// SubConn multiple times in the same pass. We don't want this.
|
||||||
|
newAddrs = deDupAddresses(newAddrs)
|
||||||
|
newAddrs = interleaveAddresses(newAddrs)
|
||||||
|
|
||||||
|
prevAddr := b.addressList.currentAddress()
|
||||||
|
prevSCData, found := b.subConns.Get(prevAddr)
|
||||||
|
prevAddrsCount := b.addressList.size()
|
||||||
|
isPrevRawConnectivityStateReady := found && prevSCData.rawConnectivityState == connectivity.Ready
|
||||||
|
b.addressList.updateAddrs(newAddrs)
|
||||||
|
|
||||||
|
// If the previous ready SubConn exists in new address list,
|
||||||
|
// keep this connection and don't create new SubConns.
|
||||||
|
if isPrevRawConnectivityStateReady && b.addressList.seekTo(prevAddr) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b.reconcileSubConnsLocked(newAddrs)
|
||||||
|
// If it's the first resolver update or the balancer was already READY
|
||||||
|
// (but the new address list does not contain the ready SubConn) or
|
||||||
|
// CONNECTING, enter CONNECTING.
|
||||||
|
// We may be in TRANSIENT_FAILURE due to a previous empty address list,
|
||||||
|
// we should still enter CONNECTING because the sticky TF behaviour
|
||||||
|
// mentioned in A62 applies only when the TRANSIENT_FAILURE is reported
|
||||||
|
// due to connectivity failures.
|
||||||
|
if isPrevRawConnectivityStateReady || b.state == connectivity.Connecting || prevAddrsCount == 0 {
|
||||||
|
// Start connection attempt at first address.
|
||||||
|
b.forceUpdateConcludedStateLocked(balancer.State{
|
||||||
|
ConnectivityState: connectivity.Connecting,
|
||||||
|
Picker: &picker{err: balancer.ErrNoSubConnAvailable},
|
||||||
|
})
|
||||||
|
b.startFirstPassLocked()
|
||||||
|
} else if b.state == connectivity.TransientFailure {
|
||||||
|
// If we're in TRANSIENT_FAILURE, we stay in TRANSIENT_FAILURE until
|
||||||
|
// we're READY. See A62.
|
||||||
|
b.startFirstPassLocked()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSubConnState is unused as a StateListener is always registered when
|
||||||
|
// creating SubConns.
|
||||||
|
func (b *pickfirstBalancer) UpdateSubConnState(subConn balancer.SubConn, state balancer.SubConnState) {
|
||||||
|
b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", subConn, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *pickfirstBalancer) Close() {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
b.closeSubConnsLocked()
|
||||||
|
b.cancelConnectionTimer()
|
||||||
|
b.state = connectivity.Shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitIdle moves the balancer out of idle state. It can be called concurrently
|
||||||
|
// by the idlePicker and clientConn so access to variables should be
|
||||||
|
// synchronized.
|
||||||
|
func (b *pickfirstBalancer) ExitIdle() {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
if b.state == connectivity.Idle {
|
||||||
|
// Move the balancer into CONNECTING state immediately. This is done to
|
||||||
|
// avoid staying in IDLE if a resolver update arrives before the first
|
||||||
|
// SubConn reports CONNECTING.
|
||||||
|
b.updateBalancerState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.Connecting,
|
||||||
|
Picker: &picker{err: balancer.ErrNoSubConnAvailable},
|
||||||
|
})
|
||||||
|
b.startFirstPassLocked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *pickfirstBalancer) startFirstPassLocked() {
|
||||||
|
b.firstPass = true
|
||||||
|
b.numTF = 0
|
||||||
|
// Reset the connection attempt record for existing SubConns.
|
||||||
|
for _, sd := range b.subConns.Values() {
|
||||||
|
sd.connectionFailedInFirstPass = false
|
||||||
|
}
|
||||||
|
b.requestConnectionLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *pickfirstBalancer) closeSubConnsLocked() {
|
||||||
|
for _, sd := range b.subConns.Values() {
|
||||||
|
sd.subConn.Shutdown()
|
||||||
|
}
|
||||||
|
b.subConns = resolver.NewAddressMapV2[*scData]()
|
||||||
|
}
|
||||||
|
|
||||||
|
// deDupAddresses ensures that each address appears only once in the slice.
|
||||||
|
func deDupAddresses(addrs []resolver.Address) []resolver.Address {
|
||||||
|
seenAddrs := resolver.NewAddressMapV2[bool]()
|
||||||
|
retAddrs := []resolver.Address{}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if _, ok := seenAddrs.Get(addr); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenAddrs.Set(addr, true)
|
||||||
|
retAddrs = append(retAddrs, addr)
|
||||||
|
}
|
||||||
|
return retAddrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// interleaveAddresses interleaves addresses of both families (IPv4 and IPv6)
|
||||||
|
// as per RFC-8305 section 4.
|
||||||
|
// Whichever address family is first in the list is followed by an address of
|
||||||
|
// the other address family; that is, if the first address in the list is IPv6,
|
||||||
|
// then the first IPv4 address should be moved up in the list to be second in
|
||||||
|
// the list. It doesn't support configuring "First Address Family Count", i.e.
|
||||||
|
// there will always be a single member of the first address family at the
|
||||||
|
// beginning of the interleaved list.
|
||||||
|
// Addresses that are neither IPv4 nor IPv6 are treated as part of a third
|
||||||
|
// "unknown" family for interleaving.
|
||||||
|
// See: https://datatracker.ietf.org/doc/html/rfc8305#autoid-6
|
||||||
|
func interleaveAddresses(addrs []resolver.Address) []resolver.Address {
|
||||||
|
familyAddrsMap := map[ipAddrFamily][]resolver.Address{}
|
||||||
|
interleavingOrder := []ipAddrFamily{}
|
||||||
|
for _, addr := range addrs {
|
||||||
|
family := addressFamily(addr.Addr)
|
||||||
|
if _, found := familyAddrsMap[family]; !found {
|
||||||
|
interleavingOrder = append(interleavingOrder, family)
|
||||||
|
}
|
||||||
|
familyAddrsMap[family] = append(familyAddrsMap[family], addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
interleavedAddrs := make([]resolver.Address, 0, len(addrs))
|
||||||
|
|
||||||
|
for curFamilyIdx := 0; len(interleavedAddrs) < len(addrs); curFamilyIdx = (curFamilyIdx + 1) % len(interleavingOrder) {
|
||||||
|
// Some IP types may have fewer addresses than others, so we look for
|
||||||
|
// the next type that has a remaining member to add to the interleaved
|
||||||
|
// list.
|
||||||
|
family := interleavingOrder[curFamilyIdx]
|
||||||
|
remainingMembers := familyAddrsMap[family]
|
||||||
|
if len(remainingMembers) > 0 {
|
||||||
|
interleavedAddrs = append(interleavedAddrs, remainingMembers[0])
|
||||||
|
familyAddrsMap[family] = remainingMembers[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return interleavedAddrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// addressFamily returns the ipAddrFamily after parsing the address string.
|
||||||
|
// If the address isn't of the format "ip-address:port", it returns
|
||||||
|
// ipAddrFamilyUnknown. The address may be valid even if it's not an IP when
|
||||||
|
// using a resolver like passthrough where the address may be a hostname in
|
||||||
|
// some format that the dialer can resolve.
|
||||||
|
func addressFamily(address string) ipAddrFamily {
|
||||||
|
// Parse the IP after removing the port.
|
||||||
|
host, _, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return ipAddrFamilyUnknown
|
||||||
|
}
|
||||||
|
ip, err := netip.ParseAddr(host)
|
||||||
|
if err != nil {
|
||||||
|
return ipAddrFamilyUnknown
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case ip.Is4() || ip.Is4In6():
|
||||||
|
return ipAddrFamilyV4
|
||||||
|
case ip.Is6():
|
||||||
|
return ipAddrFamilyV6
|
||||||
|
default:
|
||||||
|
return ipAddrFamilyUnknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconcileSubConnsLocked updates the active subchannels based on a new address
|
||||||
|
// list from the resolver. It does this by:
|
||||||
|
// - closing subchannels: any existing subchannels associated with addresses
|
||||||
|
// that are no longer in the updated list are shut down.
|
||||||
|
// - removing subchannels: entries for these closed subchannels are removed
|
||||||
|
// from the subchannel map.
|
||||||
|
//
|
||||||
|
// This ensures that the subchannel map accurately reflects the current set of
|
||||||
|
// addresses received from the name resolver.
|
||||||
|
func (b *pickfirstBalancer) reconcileSubConnsLocked(newAddrs []resolver.Address) {
|
||||||
|
newAddrsMap := resolver.NewAddressMapV2[bool]()
|
||||||
|
for _, addr := range newAddrs {
|
||||||
|
newAddrsMap.Set(addr, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, oldAddr := range b.subConns.Keys() {
|
||||||
|
if _, ok := newAddrsMap.Get(oldAddr); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val, _ := b.subConns.Get(oldAddr)
|
||||||
|
val.subConn.Shutdown()
|
||||||
|
b.subConns.Delete(oldAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutdownRemainingLocked shuts down remaining subConns. Called when a subConn
|
||||||
|
// becomes ready, which means that all other subConn must be shutdown.
|
||||||
|
func (b *pickfirstBalancer) shutdownRemainingLocked(selected *scData) {
|
||||||
|
b.cancelConnectionTimer()
|
||||||
|
for _, sd := range b.subConns.Values() {
|
||||||
|
if sd.subConn != selected.subConn {
|
||||||
|
sd.subConn.Shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.subConns = resolver.NewAddressMapV2[*scData]()
|
||||||
|
b.subConns.Set(selected.addr, selected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestConnectionLocked starts connecting on the subchannel corresponding to
|
||||||
|
// the current address. If no subchannel exists, one is created. If the current
|
||||||
|
// subchannel is in TransientFailure, a connection to the next address is
|
||||||
|
// attempted until a subchannel is found.
|
||||||
|
func (b *pickfirstBalancer) requestConnectionLocked() {
|
||||||
|
if !b.addressList.isValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var lastErr error
|
||||||
|
for valid := true; valid; valid = b.addressList.increment() {
|
||||||
|
curAddr := b.addressList.currentAddress()
|
||||||
|
sd, ok := b.subConns.Get(curAddr)
|
||||||
|
if !ok {
|
||||||
|
var err error
|
||||||
|
// We want to assign the new scData to sd from the outer scope,
|
||||||
|
// hence we can't use := below.
|
||||||
|
sd, err = b.newSCData(curAddr)
|
||||||
|
if err != nil {
|
||||||
|
// This should never happen, unless the clientConn is being shut
|
||||||
|
// down.
|
||||||
|
if b.logger.V(2) {
|
||||||
|
b.logger.Infof("Failed to create a subConn for address %v: %v", curAddr.String(), err)
|
||||||
|
}
|
||||||
|
// Do nothing, the LB policy will be closed soon.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.subConns.Set(curAddr, sd)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch sd.rawConnectivityState {
|
||||||
|
case connectivity.Idle:
|
||||||
|
sd.subConn.Connect()
|
||||||
|
b.scheduleNextConnectionLocked()
|
||||||
|
return
|
||||||
|
case connectivity.TransientFailure:
|
||||||
|
// The SubConn is being re-used and failed during a previous pass
|
||||||
|
// over the addressList. It has not completed backoff yet.
|
||||||
|
// Mark it as having failed and try the next address.
|
||||||
|
sd.connectionFailedInFirstPass = true
|
||||||
|
lastErr = sd.lastErr
|
||||||
|
continue
|
||||||
|
case connectivity.Connecting:
|
||||||
|
// Wait for the connection attempt to complete or the timer to fire
|
||||||
|
// before attempting the next address.
|
||||||
|
b.scheduleNextConnectionLocked()
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
b.logger.Errorf("SubConn with unexpected state %v present in SubConns map.", sd.rawConnectivityState)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All the remaining addresses in the list are in TRANSIENT_FAILURE, end the
|
||||||
|
// first pass if possible.
|
||||||
|
b.endFirstPassIfPossibleLocked(lastErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *pickfirstBalancer) scheduleNextConnectionLocked() {
|
||||||
|
b.cancelConnectionTimer()
|
||||||
|
if !b.addressList.hasNext() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
curAddr := b.addressList.currentAddress()
|
||||||
|
cancelled := false // Access to this is protected by the balancer's mutex.
|
||||||
|
closeFn := internal.TimeAfterFunc(connectionDelayInterval, func() {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
// If the scheduled task is cancelled while acquiring the mutex, return.
|
||||||
|
if cancelled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if b.logger.V(2) {
|
||||||
|
b.logger.Infof("Happy Eyeballs timer expired while waiting for connection to %q.", curAddr.Addr)
|
||||||
|
}
|
||||||
|
if b.addressList.increment() {
|
||||||
|
b.requestConnectionLocked()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Access to the cancellation callback held by the balancer is guarded by
|
||||||
|
// the balancer's mutex, so it's safe to set the boolean from the callback.
|
||||||
|
b.cancelConnectionTimer = sync.OnceFunc(func() {
|
||||||
|
cancelled = true
|
||||||
|
closeFn()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *pickfirstBalancer) updateSubConnState(sd *scData, newState balancer.SubConnState) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
oldState := sd.rawConnectivityState
|
||||||
|
sd.rawConnectivityState = newState.ConnectivityState
|
||||||
|
// Previously relevant SubConns can still callback with state updates.
|
||||||
|
// To prevent pickers from returning these obsolete SubConns, this logic
|
||||||
|
// is included to check if the current list of active SubConns includes this
|
||||||
|
// SubConn.
|
||||||
|
if !b.isActiveSCData(sd) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if newState.ConnectivityState == connectivity.Shutdown {
|
||||||
|
sd.effectiveState = connectivity.Shutdown
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record a connection attempt when exiting CONNECTING.
|
||||||
|
if newState.ConnectivityState == connectivity.TransientFailure {
|
||||||
|
sd.connectionFailedInFirstPass = true
|
||||||
|
connectionAttemptsFailedMetric.Record(b.metricsRecorder, 1, b.target)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newState.ConnectivityState == connectivity.Ready {
|
||||||
|
connectionAttemptsSucceededMetric.Record(b.metricsRecorder, 1, b.target)
|
||||||
|
b.shutdownRemainingLocked(sd)
|
||||||
|
if !b.addressList.seekTo(sd.addr) {
|
||||||
|
// This should not fail as we should have only one SubConn after
|
||||||
|
// entering READY. The SubConn should be present in the addressList.
|
||||||
|
b.logger.Errorf("Address %q not found address list in %v", sd.addr, b.addressList.addresses)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !b.healthCheckingEnabled {
|
||||||
|
if b.logger.V(2) {
|
||||||
|
b.logger.Infof("SubConn %p reported connectivity state READY and the health listener is disabled. Transitioning SubConn to READY.", sd.subConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
sd.effectiveState = connectivity.Ready
|
||||||
|
b.updateBalancerState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.Ready,
|
||||||
|
Picker: &picker{result: balancer.PickResult{SubConn: sd.subConn}},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if b.logger.V(2) {
|
||||||
|
b.logger.Infof("SubConn %p reported connectivity state READY. Registering health listener.", sd.subConn)
|
||||||
|
}
|
||||||
|
// Send a CONNECTING update to take the SubConn out of sticky-TF if
|
||||||
|
// required.
|
||||||
|
sd.effectiveState = connectivity.Connecting
|
||||||
|
b.updateBalancerState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.Connecting,
|
||||||
|
Picker: &picker{err: balancer.ErrNoSubConnAvailable},
|
||||||
|
})
|
||||||
|
sd.subConn.RegisterHealthListener(func(scs balancer.SubConnState) {
|
||||||
|
b.updateSubConnHealthState(sd, scs)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the LB policy is READY, and it receives a subchannel state change,
|
||||||
|
// it means that the READY subchannel has failed.
|
||||||
|
// A SubConn can also transition from CONNECTING directly to IDLE when
|
||||||
|
// a transport is successfully created, but the connection fails
|
||||||
|
// before the SubConn can send the notification for READY. We treat
|
||||||
|
// this as a successful connection and transition to IDLE.
|
||||||
|
// TODO: https://github.com/grpc/grpc-go/issues/7862 - Remove the second
|
||||||
|
// part of the if condition below once the issue is fixed.
|
||||||
|
if oldState == connectivity.Ready || (oldState == connectivity.Connecting && newState.ConnectivityState == connectivity.Idle) {
|
||||||
|
// Once a transport fails, the balancer enters IDLE and starts from
|
||||||
|
// the first address when the picker is used.
|
||||||
|
b.shutdownRemainingLocked(sd)
|
||||||
|
sd.effectiveState = newState.ConnectivityState
|
||||||
|
// READY SubConn interspliced in between CONNECTING and IDLE, need to
|
||||||
|
// account for that.
|
||||||
|
if oldState == connectivity.Connecting {
|
||||||
|
// A known issue (https://github.com/grpc/grpc-go/issues/7862)
|
||||||
|
// causes a race that prevents the READY state change notification.
|
||||||
|
// This works around it.
|
||||||
|
connectionAttemptsSucceededMetric.Record(b.metricsRecorder, 1, b.target)
|
||||||
|
}
|
||||||
|
disconnectionsMetric.Record(b.metricsRecorder, 1, b.target)
|
||||||
|
b.addressList.reset()
|
||||||
|
b.updateBalancerState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.Idle,
|
||||||
|
Picker: &idlePicker{exitIdle: sync.OnceFunc(b.ExitIdle)},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.firstPass {
|
||||||
|
switch newState.ConnectivityState {
|
||||||
|
case connectivity.Connecting:
|
||||||
|
// The effective state can be in either IDLE, CONNECTING or
|
||||||
|
// TRANSIENT_FAILURE. If it's TRANSIENT_FAILURE, stay in
|
||||||
|
// TRANSIENT_FAILURE until it's READY. See A62.
|
||||||
|
if sd.effectiveState != connectivity.TransientFailure {
|
||||||
|
sd.effectiveState = connectivity.Connecting
|
||||||
|
b.updateBalancerState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.Connecting,
|
||||||
|
Picker: &picker{err: balancer.ErrNoSubConnAvailable},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case connectivity.TransientFailure:
|
||||||
|
sd.lastErr = newState.ConnectionError
|
||||||
|
sd.effectiveState = connectivity.TransientFailure
|
||||||
|
// Since we're re-using common SubConns while handling resolver
|
||||||
|
// updates, we could receive an out of turn TRANSIENT_FAILURE from
|
||||||
|
// a pass over the previous address list. Happy Eyeballs will also
|
||||||
|
// cause out of order updates to arrive.
|
||||||
|
|
||||||
|
if curAddr := b.addressList.currentAddress(); equalAddressIgnoringBalAttributes(&curAddr, &sd.addr) {
|
||||||
|
b.cancelConnectionTimer()
|
||||||
|
if b.addressList.increment() {
|
||||||
|
b.requestConnectionLocked()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// End the first pass if we've seen a TRANSIENT_FAILURE from all
|
||||||
|
// SubConns once.
|
||||||
|
b.endFirstPassIfPossibleLocked(newState.ConnectionError)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have finished the first pass, keep re-connecting failing SubConns.
|
||||||
|
switch newState.ConnectivityState {
|
||||||
|
case connectivity.TransientFailure:
|
||||||
|
b.numTF = (b.numTF + 1) % b.subConns.Len()
|
||||||
|
sd.lastErr = newState.ConnectionError
|
||||||
|
if b.numTF%b.subConns.Len() == 0 {
|
||||||
|
b.updateBalancerState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.TransientFailure,
|
||||||
|
Picker: &picker{err: newState.ConnectionError},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// We don't need to request re-resolution since the SubConn already
|
||||||
|
// does that before reporting TRANSIENT_FAILURE.
|
||||||
|
// TODO: #7534 - Move re-resolution requests from SubConn into
|
||||||
|
// pick_first.
|
||||||
|
case connectivity.Idle:
|
||||||
|
sd.subConn.Connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endFirstPassIfPossibleLocked ends the first happy-eyeballs pass if all the
|
||||||
|
// addresses are tried and their SubConns have reported a failure.
|
||||||
|
func (b *pickfirstBalancer) endFirstPassIfPossibleLocked(lastErr error) {
|
||||||
|
// An optimization to avoid iterating over the entire SubConn map.
|
||||||
|
if b.addressList.isValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Connect() has been called on all the SubConns. The first pass can be
|
||||||
|
// ended if all the SubConns have reported a failure.
|
||||||
|
for _, sd := range b.subConns.Values() {
|
||||||
|
if !sd.connectionFailedInFirstPass {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.firstPass = false
|
||||||
|
b.updateBalancerState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.TransientFailure,
|
||||||
|
Picker: &picker{err: lastErr},
|
||||||
|
})
|
||||||
|
// Start re-connecting all the SubConns that are already in IDLE.
|
||||||
|
for _, sd := range b.subConns.Values() {
|
||||||
|
if sd.rawConnectivityState == connectivity.Idle {
|
||||||
|
sd.subConn.Connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *pickfirstBalancer) isActiveSCData(sd *scData) bool {
|
||||||
|
activeSD, found := b.subConns.Get(sd.addr)
|
||||||
|
return found && activeSD == sd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *pickfirstBalancer) updateSubConnHealthState(sd *scData, state balancer.SubConnState) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
// Previously relevant SubConns can still callback with state updates.
|
||||||
|
// To prevent pickers from returning these obsolete SubConns, this logic
|
||||||
|
// is included to check if the current list of active SubConns includes
|
||||||
|
// this SubConn.
|
||||||
|
if !b.isActiveSCData(sd) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sd.effectiveState = state.ConnectivityState
|
||||||
|
switch state.ConnectivityState {
|
||||||
|
case connectivity.Ready:
|
||||||
|
b.updateBalancerState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.Ready,
|
||||||
|
Picker: &picker{result: balancer.PickResult{SubConn: sd.subConn}},
|
||||||
|
})
|
||||||
|
case connectivity.TransientFailure:
|
||||||
|
b.updateBalancerState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.TransientFailure,
|
||||||
|
Picker: &picker{err: fmt.Errorf("pickfirst: health check failure: %v", state.ConnectionError)},
|
||||||
|
})
|
||||||
|
case connectivity.Connecting:
|
||||||
|
b.updateBalancerState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.Connecting,
|
||||||
|
Picker: &picker{err: balancer.ErrNoSubConnAvailable},
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
b.logger.Errorf("Got unexpected health update for SubConn %p: %v", state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateBalancerState stores the state reported to the channel and calls
|
||||||
|
// ClientConn.UpdateState(). As an optimization, it avoids sending duplicate
|
||||||
|
// updates to the channel.
|
||||||
|
func (b *pickfirstBalancer) updateBalancerState(newState balancer.State) {
|
||||||
|
// In case of TransientFailures allow the picker to be updated to update
|
||||||
|
// the connectivity error, in all other cases don't send duplicate state
|
||||||
|
// updates.
|
||||||
|
if newState.ConnectivityState == b.state && b.state != connectivity.TransientFailure {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.forceUpdateConcludedStateLocked(newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// forceUpdateConcludedStateLocked stores the state reported to the channel and
|
||||||
|
// calls ClientConn.UpdateState().
|
||||||
|
// A separate function is defined to force update the ClientConn state since the
|
||||||
|
// channel doesn't correctly assume that LB policies start in CONNECTING and
|
||||||
|
// relies on LB policy to send an initial CONNECTING update.
|
||||||
|
func (b *pickfirstBalancer) forceUpdateConcludedStateLocked(newState balancer.State) {
|
||||||
|
b.state = newState.ConnectivityState
|
||||||
|
b.cc.UpdateState(newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
type picker struct {
|
||||||
|
result balancer.PickResult
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *picker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
|
||||||
|
return p.result, p.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// idlePicker is used when the SubConn is IDLE and kicks the SubConn into
|
||||||
|
// CONNECTING when Pick is called.
|
||||||
|
type idlePicker struct {
|
||||||
|
exitIdle func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *idlePicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
|
||||||
|
i.exitIdle()
|
||||||
|
return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
// addressList manages sequentially iterating over addresses present in a list
|
||||||
|
// of endpoints. It provides a 1 dimensional view of the addresses present in
|
||||||
|
// the endpoints.
|
||||||
|
// This type is not safe for concurrent access.
|
||||||
|
type addressList struct {
|
||||||
|
addresses []resolver.Address
|
||||||
|
idx int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (al *addressList) isValid() bool {
|
||||||
|
return al.idx < len(al.addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (al *addressList) size() int {
|
||||||
|
return len(al.addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
// increment moves to the next index in the address list.
|
||||||
|
// This method returns false if it went off the list, true otherwise.
|
||||||
|
func (al *addressList) increment() bool {
|
||||||
|
if !al.isValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
al.idx++
|
||||||
|
return al.idx < len(al.addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
// currentAddress returns the current address pointed to in the addressList.
|
||||||
|
// If the list is in an invalid state, it returns an empty address instead.
|
||||||
|
func (al *addressList) currentAddress() resolver.Address {
|
||||||
|
if !al.isValid() {
|
||||||
|
return resolver.Address{}
|
||||||
|
}
|
||||||
|
return al.addresses[al.idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (al *addressList) reset() {
|
||||||
|
al.idx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (al *addressList) updateAddrs(addrs []resolver.Address) {
|
||||||
|
al.addresses = addrs
|
||||||
|
al.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// seekTo returns false if the needle was not found and the current index was
|
||||||
|
// left unchanged.
|
||||||
|
func (al *addressList) seekTo(needle resolver.Address) bool {
|
||||||
|
for ai, addr := range al.addresses {
|
||||||
|
if !equalAddressIgnoringBalAttributes(&addr, &needle) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
al.idx = ai
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasNext returns whether incrementing the addressList will result in moving
|
||||||
|
// past the end of the list. If the list has already moved past the end, it
|
||||||
|
// returns false.
|
||||||
|
func (al *addressList) hasNext() bool {
|
||||||
|
if !al.isValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return al.idx+1 < len(al.addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
// equalAddressIgnoringBalAttributes returns true is a and b are considered
|
||||||
|
// equal. This is different from the Equal method on the resolver.Address type
|
||||||
|
// which considers all fields to determine equality. Here, we only consider
|
||||||
|
// fields that are meaningful to the SubConn.
|
||||||
|
func equalAddressIgnoringBalAttributes(a, b *resolver.Address) bool {
|
||||||
|
return a.Addr == b.Addr && a.ServerName == b.ServerName &&
|
||||||
|
a.Attributes.Equal(b.Attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// weightAttribute is a convenience function which returns the value of the
|
||||||
|
// weight endpoint Attribute.
|
||||||
|
//
|
||||||
|
// When used in the xDS context, the weight attribute is guaranteed to be
|
||||||
|
// non-zero. But, when used in a non-xDS context, the weight attribute could be
|
||||||
|
// unset. A Default of 1 is used in the latter case.
|
||||||
|
func weightAttribute(e resolver.Endpoint) uint32 {
|
||||||
|
w := weight.FromEndpoint(e).Weight
|
||||||
|
if w == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 roundrobin defines a roundrobin balancer. Roundrobin balancer is
|
||||||
|
// installed as one of the default balancers in gRPC, users don't need to
|
||||||
|
// explicitly install this balancer.
|
||||||
|
package roundrobin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/balancer/endpointsharding"
|
||||||
|
"google.golang.org/grpc/balancer/pickfirst"
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
internalgrpclog "google.golang.org/grpc/internal/grpclog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Name is the name of round_robin balancer.
|
||||||
|
const Name = "round_robin"
|
||||||
|
|
||||||
|
var logger = grpclog.Component("roundrobin")
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
balancer.Register(builder{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type builder struct{}
|
||||||
|
|
||||||
|
func (bb builder) Name() string {
|
||||||
|
return Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bb builder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
|
||||||
|
childBuilder := balancer.Get(pickfirst.Name).Build
|
||||||
|
bal := &rrBalancer{
|
||||||
|
cc: cc,
|
||||||
|
Balancer: endpointsharding.NewBalancer(cc, opts, childBuilder, endpointsharding.Options{}),
|
||||||
|
}
|
||||||
|
bal.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf("[%p] ", bal))
|
||||||
|
bal.logger.Infof("Created")
|
||||||
|
return bal
|
||||||
|
}
|
||||||
|
|
||||||
|
type rrBalancer struct {
|
||||||
|
balancer.Balancer
|
||||||
|
cc balancer.ClientConn
|
||||||
|
logger *internalgrpclog.PrefixLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *rrBalancer) UpdateClientConnState(ccs balancer.ClientConnState) error {
|
||||||
|
return b.Balancer.UpdateClientConnState(balancer.ClientConnState{
|
||||||
|
// Enable the health listener in pickfirst children for client side health
|
||||||
|
// checks and outlier detection, if configured.
|
||||||
|
ResolverState: pickfirst.EnableHealthListener(ccs.ResolverState),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 balancer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
"google.golang.org/grpc/internal"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A SubConn represents a single connection to a gRPC backend service.
|
||||||
|
//
|
||||||
|
// All SubConns start in IDLE, and will not try to connect. To trigger a
|
||||||
|
// connection attempt, Balancers must call Connect.
|
||||||
|
//
|
||||||
|
// If the connection attempt fails, the SubConn will transition to
|
||||||
|
// TRANSIENT_FAILURE for a backoff period, and then return to IDLE. If the
|
||||||
|
// connection attempt succeeds, it will transition to READY.
|
||||||
|
//
|
||||||
|
// If a READY SubConn becomes disconnected, the SubConn will transition to IDLE.
|
||||||
|
//
|
||||||
|
// If a connection re-enters IDLE, Balancers must call Connect again to trigger
|
||||||
|
// a new connection attempt.
|
||||||
|
//
|
||||||
|
// Each SubConn contains a list of addresses. gRPC will try to connect to the
|
||||||
|
// addresses in sequence, and stop trying the remainder once the first
|
||||||
|
// connection is successful. However, this behavior is deprecated. SubConns
|
||||||
|
// should only use a single address.
|
||||||
|
//
|
||||||
|
// NOTICE: This interface is intended to be implemented by gRPC, or intercepted
|
||||||
|
// by custom load balancing polices. Users should not need their own complete
|
||||||
|
// implementation of this interface -- they should always delegate to a SubConn
|
||||||
|
// returned by ClientConn.NewSubConn() by embedding it in their implementations.
|
||||||
|
// An embedded SubConn must never be nil, or runtime panics will occur.
|
||||||
|
type SubConn interface {
|
||||||
|
// UpdateAddresses updates the addresses used in this SubConn.
|
||||||
|
// gRPC checks if currently-connected address is still in the new list.
|
||||||
|
// If it's in the list, the connection will be kept.
|
||||||
|
// If it's not in the list, the connection will gracefully close, and
|
||||||
|
// a new connection will be created.
|
||||||
|
//
|
||||||
|
// This will trigger a state transition for the SubConn.
|
||||||
|
//
|
||||||
|
// Deprecated: this method will be removed. Create new SubConns for new
|
||||||
|
// addresses instead.
|
||||||
|
UpdateAddresses([]resolver.Address)
|
||||||
|
// Connect starts the connecting for this SubConn.
|
||||||
|
Connect()
|
||||||
|
// GetOrBuildProducer returns a reference to the existing Producer for this
|
||||||
|
// ProducerBuilder in this SubConn, or, if one does not currently exist,
|
||||||
|
// creates a new one and returns it. Returns a close function which may be
|
||||||
|
// called when the Producer is no longer needed. Otherwise the producer
|
||||||
|
// will automatically be closed upon connection loss or subchannel close.
|
||||||
|
// Should only be called on a SubConn in state Ready. Otherwise the
|
||||||
|
// producer will be unable to create streams.
|
||||||
|
GetOrBuildProducer(ProducerBuilder) (p Producer, close func())
|
||||||
|
// Shutdown shuts down the SubConn gracefully. Any started RPCs will be
|
||||||
|
// allowed to complete. No future calls should be made on the SubConn.
|
||||||
|
// One final state update will be delivered to the StateListener (or
|
||||||
|
// UpdateSubConnState; deprecated) with ConnectivityState of Shutdown to
|
||||||
|
// indicate the shutdown operation. This may be delivered before
|
||||||
|
// in-progress RPCs are complete and the actual connection is closed.
|
||||||
|
Shutdown()
|
||||||
|
// RegisterHealthListener registers a health listener that receives health
|
||||||
|
// updates for a Ready SubConn. Only one health listener can be registered
|
||||||
|
// at a time. A health listener should be registered each time the SubConn's
|
||||||
|
// connectivity state changes to READY. Registering a health listener when
|
||||||
|
// the connectivity state is not READY may result in undefined behaviour.
|
||||||
|
// This method must not be called synchronously while handling an update
|
||||||
|
// from a previously registered health listener.
|
||||||
|
RegisterHealthListener(func(SubConnState))
|
||||||
|
// EnforceSubConnEmbedding is included to force implementers to embed
|
||||||
|
// another implementation of this interface, allowing gRPC to add methods
|
||||||
|
// without breaking users.
|
||||||
|
internal.EnforceSubConnEmbedding
|
||||||
|
}
|
||||||
|
|
||||||
|
// A ProducerBuilder is a simple constructor for a Producer. It is used by the
|
||||||
|
// SubConn to create producers when needed.
|
||||||
|
type ProducerBuilder interface {
|
||||||
|
// Build creates a Producer. The first parameter is always a
|
||||||
|
// grpc.ClientConnInterface (a type to allow creating RPCs/streams on the
|
||||||
|
// associated SubConn), but is declared as `any` to avoid a dependency
|
||||||
|
// cycle. Build also returns a close function that will be called when all
|
||||||
|
// references to the Producer have been given up for a SubConn, or when a
|
||||||
|
// connectivity state change occurs on the SubConn. The close function
|
||||||
|
// should always block until all asynchronous cleanup work is completed.
|
||||||
|
Build(grpcClientConnInterface any) (p Producer, close func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubConnState describes the state of a SubConn.
|
||||||
|
type SubConnState struct {
|
||||||
|
// ConnectivityState is the connectivity state of the SubConn.
|
||||||
|
ConnectivityState connectivity.State
|
||||||
|
// ConnectionError is set if the ConnectivityState is TransientFailure,
|
||||||
|
// describing the reason the SubConn failed. Otherwise, it is nil.
|
||||||
|
ConnectionError error
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Producer is a type shared among potentially many consumers. It is
|
||||||
|
// associated with a SubConn, and an implementation will typically contain
|
||||||
|
// other methods to provide additional functionality, e.g. configuration or
|
||||||
|
// subscription registration.
|
||||||
|
type Producer any
|
||||||
|
|
@ -0,0 +1,517 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
"google.golang.org/grpc/experimental/stats"
|
||||||
|
"google.golang.org/grpc/internal"
|
||||||
|
"google.golang.org/grpc/internal/balancer/gracefulswitch"
|
||||||
|
"google.golang.org/grpc/internal/channelz"
|
||||||
|
"google.golang.org/grpc/internal/grpcsync"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// noOpRegisterHealthListenerFn is used when client side health checking is
|
||||||
|
// disabled. It sends a single READY update on the registered listener.
|
||||||
|
noOpRegisterHealthListenerFn = func(_ context.Context, listener func(balancer.SubConnState)) func() {
|
||||||
|
listener(balancer.SubConnState{ConnectivityState: connectivity.Ready})
|
||||||
|
return func() {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ccBalancerWrapper sits between the ClientConn and the Balancer.
|
||||||
|
//
|
||||||
|
// ccBalancerWrapper implements methods corresponding to the ones on the
|
||||||
|
// balancer.Balancer interface. The ClientConn is free to call these methods
|
||||||
|
// concurrently and the ccBalancerWrapper ensures that calls from the ClientConn
|
||||||
|
// to the Balancer happen in order by performing them in the serializer, without
|
||||||
|
// any mutexes held.
|
||||||
|
//
|
||||||
|
// ccBalancerWrapper also implements the balancer.ClientConn interface and is
|
||||||
|
// passed to the Balancer implementations. It invokes unexported methods on the
|
||||||
|
// ClientConn to handle these calls from the Balancer.
|
||||||
|
//
|
||||||
|
// It uses the gracefulswitch.Balancer internally to ensure that balancer
|
||||||
|
// switches happen in a graceful manner.
|
||||||
|
type ccBalancerWrapper struct {
|
||||||
|
internal.EnforceClientConnEmbedding
|
||||||
|
// The following fields are initialized when the wrapper is created and are
|
||||||
|
// read-only afterwards, and therefore can be accessed without a mutex.
|
||||||
|
cc *ClientConn
|
||||||
|
opts balancer.BuildOptions
|
||||||
|
serializer *grpcsync.CallbackSerializer
|
||||||
|
serializerCancel context.CancelFunc
|
||||||
|
|
||||||
|
// The following fields are only accessed within the serializer or during
|
||||||
|
// initialization.
|
||||||
|
curBalancerName string
|
||||||
|
balancer *gracefulswitch.Balancer
|
||||||
|
|
||||||
|
// The following field is protected by mu. Caller must take cc.mu before
|
||||||
|
// taking mu.
|
||||||
|
mu sync.Mutex
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCCBalancerWrapper creates a new balancer wrapper in idle state. The
|
||||||
|
// underlying balancer is not created until the updateClientConnState() method
|
||||||
|
// is invoked.
|
||||||
|
func newCCBalancerWrapper(cc *ClientConn) *ccBalancerWrapper {
|
||||||
|
ctx, cancel := context.WithCancel(cc.ctx)
|
||||||
|
ccb := &ccBalancerWrapper{
|
||||||
|
cc: cc,
|
||||||
|
opts: balancer.BuildOptions{
|
||||||
|
DialCreds: cc.dopts.copts.TransportCredentials,
|
||||||
|
CredsBundle: cc.dopts.copts.CredsBundle,
|
||||||
|
Dialer: cc.dopts.copts.Dialer,
|
||||||
|
Authority: cc.authority,
|
||||||
|
CustomUserAgent: cc.dopts.copts.UserAgent,
|
||||||
|
ChannelzParent: cc.channelz,
|
||||||
|
Target: cc.parsedTarget,
|
||||||
|
},
|
||||||
|
serializer: grpcsync.NewCallbackSerializer(ctx),
|
||||||
|
serializerCancel: cancel,
|
||||||
|
}
|
||||||
|
ccb.balancer = gracefulswitch.NewBalancer(ccb, ccb.opts)
|
||||||
|
return ccb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ccb *ccBalancerWrapper) MetricsRecorder() stats.MetricsRecorder {
|
||||||
|
return ccb.cc.metricsRecorderList
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateClientConnState is invoked by grpc to push a ClientConnState update to
|
||||||
|
// the underlying balancer. This is always executed from the serializer, so
|
||||||
|
// it is safe to call into the balancer here.
|
||||||
|
func (ccb *ccBalancerWrapper) updateClientConnState(ccs *balancer.ClientConnState) error {
|
||||||
|
errCh := make(chan error)
|
||||||
|
uccs := func(ctx context.Context) {
|
||||||
|
defer close(errCh)
|
||||||
|
if ctx.Err() != nil || ccb.balancer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := gracefulswitch.ChildName(ccs.BalancerConfig)
|
||||||
|
if ccb.curBalancerName != name {
|
||||||
|
ccb.curBalancerName = name
|
||||||
|
channelz.Infof(logger, ccb.cc.channelz, "Channel switches to new LB policy %q", name)
|
||||||
|
}
|
||||||
|
err := ccb.balancer.UpdateClientConnState(*ccs)
|
||||||
|
if logger.V(2) && err != nil {
|
||||||
|
logger.Infof("error from balancer.UpdateClientConnState: %v", err)
|
||||||
|
}
|
||||||
|
errCh <- err
|
||||||
|
}
|
||||||
|
onFailure := func() { close(errCh) }
|
||||||
|
|
||||||
|
// UpdateClientConnState can race with Close, and when the latter wins, the
|
||||||
|
// serializer is closed, and the attempt to schedule the callback will fail.
|
||||||
|
// It is acceptable to ignore this failure. But since we want to handle the
|
||||||
|
// state update in a blocking fashion (when we successfully schedule the
|
||||||
|
// callback), we have to use the ScheduleOr method and not the MaybeSchedule
|
||||||
|
// method on the serializer.
|
||||||
|
ccb.serializer.ScheduleOr(uccs, onFailure)
|
||||||
|
return <-errCh
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolverError is invoked by grpc to push a resolver error to the underlying
|
||||||
|
// balancer. The call to the balancer is executed from the serializer.
|
||||||
|
func (ccb *ccBalancerWrapper) resolverError(err error) {
|
||||||
|
ccb.serializer.TrySchedule(func(ctx context.Context) {
|
||||||
|
if ctx.Err() != nil || ccb.balancer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ccb.balancer.ResolverError(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// close initiates async shutdown of the wrapper. cc.mu must be held when
|
||||||
|
// calling this function. To determine the wrapper has finished shutting down,
|
||||||
|
// the channel should block on ccb.serializer.Done() without cc.mu held.
|
||||||
|
func (ccb *ccBalancerWrapper) close() {
|
||||||
|
ccb.mu.Lock()
|
||||||
|
ccb.closed = true
|
||||||
|
ccb.mu.Unlock()
|
||||||
|
channelz.Info(logger, ccb.cc.channelz, "ccBalancerWrapper: closing")
|
||||||
|
ccb.serializer.TrySchedule(func(context.Context) {
|
||||||
|
if ccb.balancer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ccb.balancer.Close()
|
||||||
|
ccb.balancer = nil
|
||||||
|
})
|
||||||
|
ccb.serializerCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// exitIdle invokes the balancer's exitIdle method in the serializer.
|
||||||
|
func (ccb *ccBalancerWrapper) exitIdle() {
|
||||||
|
ccb.serializer.TrySchedule(func(ctx context.Context) {
|
||||||
|
if ctx.Err() != nil || ccb.balancer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ccb.balancer.ExitIdle()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ccb *ccBalancerWrapper) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) {
|
||||||
|
ccb.cc.mu.Lock()
|
||||||
|
defer ccb.cc.mu.Unlock()
|
||||||
|
|
||||||
|
ccb.mu.Lock()
|
||||||
|
if ccb.closed {
|
||||||
|
ccb.mu.Unlock()
|
||||||
|
return nil, fmt.Errorf("balancer is being closed; no new SubConns allowed")
|
||||||
|
}
|
||||||
|
ccb.mu.Unlock()
|
||||||
|
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
return nil, fmt.Errorf("grpc: cannot create SubConn with empty address list")
|
||||||
|
}
|
||||||
|
ac, err := ccb.cc.newAddrConnLocked(addrs, opts)
|
||||||
|
if err != nil {
|
||||||
|
channelz.Warningf(logger, ccb.cc.channelz, "acBalancerWrapper: NewSubConn: failed to newAddrConn: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
acbw := &acBalancerWrapper{
|
||||||
|
ccb: ccb,
|
||||||
|
ac: ac,
|
||||||
|
producers: make(map[balancer.ProducerBuilder]*refCountedProducer),
|
||||||
|
stateListener: opts.StateListener,
|
||||||
|
healthData: newHealthData(connectivity.Idle),
|
||||||
|
}
|
||||||
|
ac.acbw = acbw
|
||||||
|
return acbw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ccb *ccBalancerWrapper) RemoveSubConn(balancer.SubConn) {
|
||||||
|
// The graceful switch balancer will never call this.
|
||||||
|
logger.Errorf("ccb RemoveSubConn(%v) called unexpectedly, sc")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ccb *ccBalancerWrapper) UpdateAddresses(sc balancer.SubConn, addrs []resolver.Address) {
|
||||||
|
acbw, ok := sc.(*acBalancerWrapper)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acbw.UpdateAddresses(addrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ccb *ccBalancerWrapper) UpdateState(s balancer.State) {
|
||||||
|
ccb.cc.mu.Lock()
|
||||||
|
defer ccb.cc.mu.Unlock()
|
||||||
|
if ccb.cc.conns == nil {
|
||||||
|
// The CC has been closed; ignore this update.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ccb.mu.Lock()
|
||||||
|
if ccb.closed {
|
||||||
|
ccb.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ccb.mu.Unlock()
|
||||||
|
// Update picker before updating state. Even though the ordering here does
|
||||||
|
// not matter, it can lead to multiple calls of Pick in the common start-up
|
||||||
|
// case where we wait for ready and then perform an RPC. If the picker is
|
||||||
|
// updated later, we could call the "connecting" picker when the state is
|
||||||
|
// updated, and then call the "ready" picker after the picker gets updated.
|
||||||
|
|
||||||
|
// Note that there is no need to check if the balancer wrapper was closed,
|
||||||
|
// as we know the graceful switch LB policy will not call cc if it has been
|
||||||
|
// closed.
|
||||||
|
ccb.cc.pickerWrapper.updatePicker(s.Picker)
|
||||||
|
ccb.cc.csMgr.updateState(s.ConnectivityState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ccb *ccBalancerWrapper) ResolveNow(o resolver.ResolveNowOptions) {
|
||||||
|
ccb.cc.mu.RLock()
|
||||||
|
defer ccb.cc.mu.RUnlock()
|
||||||
|
|
||||||
|
ccb.mu.Lock()
|
||||||
|
if ccb.closed {
|
||||||
|
ccb.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ccb.mu.Unlock()
|
||||||
|
ccb.cc.resolveNowLocked(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ccb *ccBalancerWrapper) Target() string {
|
||||||
|
return ccb.cc.target
|
||||||
|
}
|
||||||
|
|
||||||
|
// acBalancerWrapper is a wrapper on top of ac for balancers.
|
||||||
|
// It implements balancer.SubConn interface.
|
||||||
|
type acBalancerWrapper struct {
|
||||||
|
internal.EnforceSubConnEmbedding
|
||||||
|
ac *addrConn // read-only
|
||||||
|
ccb *ccBalancerWrapper // read-only
|
||||||
|
stateListener func(balancer.SubConnState)
|
||||||
|
|
||||||
|
producersMu sync.Mutex
|
||||||
|
producers map[balancer.ProducerBuilder]*refCountedProducer
|
||||||
|
|
||||||
|
// Access to healthData is protected by healthMu.
|
||||||
|
healthMu sync.Mutex
|
||||||
|
// healthData is stored as a pointer to detect when the health listener is
|
||||||
|
// dropped or updated. This is required as closures can't be compared for
|
||||||
|
// equality.
|
||||||
|
healthData *healthData
|
||||||
|
}
|
||||||
|
|
||||||
|
// healthData holds data related to health state reporting.
|
||||||
|
type healthData struct {
|
||||||
|
// connectivityState stores the most recent connectivity state delivered
|
||||||
|
// to the LB policy. This is stored to avoid sending updates when the
|
||||||
|
// SubConn has already exited connectivity state READY.
|
||||||
|
connectivityState connectivity.State
|
||||||
|
// closeHealthProducer stores function to close the ref counted health
|
||||||
|
// producer. The health producer is automatically closed when the SubConn
|
||||||
|
// state changes.
|
||||||
|
closeHealthProducer func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHealthData(s connectivity.State) *healthData {
|
||||||
|
return &healthData{
|
||||||
|
connectivityState: s,
|
||||||
|
closeHealthProducer: func() {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateState is invoked by grpc to push a subConn state update to the
|
||||||
|
// underlying balancer.
|
||||||
|
func (acbw *acBalancerWrapper) updateState(s connectivity.State, err error) {
|
||||||
|
acbw.ccb.serializer.TrySchedule(func(ctx context.Context) {
|
||||||
|
if ctx.Err() != nil || acbw.ccb.balancer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Invalidate all producers on any state change.
|
||||||
|
acbw.closeProducers()
|
||||||
|
|
||||||
|
// Even though it is optional for balancers, gracefulswitch ensures
|
||||||
|
// opts.StateListener is set, so this cannot ever be nil.
|
||||||
|
// TODO: delete this comment when UpdateSubConnState is removed.
|
||||||
|
scs := balancer.SubConnState{ConnectivityState: s, ConnectionError: err}
|
||||||
|
// Invalidate the health listener by updating the healthData.
|
||||||
|
acbw.healthMu.Lock()
|
||||||
|
// A race may occur if a health listener is registered soon after the
|
||||||
|
// connectivity state is set but before the stateListener is called.
|
||||||
|
// Two cases may arise:
|
||||||
|
// 1. The new state is not READY: RegisterHealthListener has checks to
|
||||||
|
// ensure no updates are sent when the connectivity state is not
|
||||||
|
// READY.
|
||||||
|
// 2. The new state is READY: This means that the old state wasn't Ready.
|
||||||
|
// The RegisterHealthListener API mentions that a health listener
|
||||||
|
// must not be registered when a SubConn is not ready to avoid such
|
||||||
|
// races. When this happens, the LB policy would get health updates
|
||||||
|
// on the old listener. When the LB policy registers a new listener
|
||||||
|
// on receiving the connectivity update, the health updates will be
|
||||||
|
// sent to the new health listener.
|
||||||
|
acbw.healthData = newHealthData(scs.ConnectivityState)
|
||||||
|
acbw.healthMu.Unlock()
|
||||||
|
|
||||||
|
acbw.stateListener(scs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acbw *acBalancerWrapper) String() string {
|
||||||
|
return fmt.Sprintf("SubConn(id:%d)", acbw.ac.channelz.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acbw *acBalancerWrapper) UpdateAddresses(addrs []resolver.Address) {
|
||||||
|
acbw.ac.updateAddrs(addrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acbw *acBalancerWrapper) Connect() {
|
||||||
|
go acbw.ac.connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acbw *acBalancerWrapper) Shutdown() {
|
||||||
|
acbw.closeProducers()
|
||||||
|
acbw.ccb.cc.removeAddrConn(acbw.ac, errConnDrain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStream begins a streaming RPC on the addrConn. If the addrConn is not
|
||||||
|
// ready, blocks until it is or ctx expires. Returns an error when the context
|
||||||
|
// expires or the addrConn is shut down.
|
||||||
|
func (acbw *acBalancerWrapper) NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error) {
|
||||||
|
transport := acbw.ac.getReadyTransport()
|
||||||
|
if transport == nil {
|
||||||
|
return nil, status.Errorf(codes.Unavailable, "SubConn state is not Ready")
|
||||||
|
|
||||||
|
}
|
||||||
|
return newNonRetryClientStream(ctx, desc, method, transport, acbw.ac, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke performs a unary RPC. If the addrConn is not ready, returns
|
||||||
|
// errSubConnNotReady.
|
||||||
|
func (acbw *acBalancerWrapper) Invoke(ctx context.Context, method string, args any, reply any, opts ...CallOption) error {
|
||||||
|
cs, err := acbw.NewStream(ctx, unaryStreamDesc, method, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cs.SendMsg(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return cs.RecvMsg(reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
type refCountedProducer struct {
|
||||||
|
producer balancer.Producer
|
||||||
|
refs int // number of current refs to the producer
|
||||||
|
close func() // underlying producer's close function
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acbw *acBalancerWrapper) GetOrBuildProducer(pb balancer.ProducerBuilder) (balancer.Producer, func()) {
|
||||||
|
acbw.producersMu.Lock()
|
||||||
|
defer acbw.producersMu.Unlock()
|
||||||
|
|
||||||
|
// Look up existing producer from this builder.
|
||||||
|
pData := acbw.producers[pb]
|
||||||
|
if pData == nil {
|
||||||
|
// Not found; create a new one and add it to the producers map.
|
||||||
|
p, closeFn := pb.Build(acbw)
|
||||||
|
pData = &refCountedProducer{producer: p, close: closeFn}
|
||||||
|
acbw.producers[pb] = pData
|
||||||
|
}
|
||||||
|
// Account for this new reference.
|
||||||
|
pData.refs++
|
||||||
|
|
||||||
|
// Return a cleanup function wrapped in a OnceFunc to remove this reference
|
||||||
|
// and delete the refCountedProducer from the map if the total reference
|
||||||
|
// count goes to zero.
|
||||||
|
unref := func() {
|
||||||
|
acbw.producersMu.Lock()
|
||||||
|
// If closeProducers has already closed this producer instance, refs is
|
||||||
|
// set to 0, so the check after decrementing will never pass, and the
|
||||||
|
// producer will not be double-closed.
|
||||||
|
pData.refs--
|
||||||
|
if pData.refs == 0 {
|
||||||
|
defer pData.close() // Run outside the acbw mutex
|
||||||
|
delete(acbw.producers, pb)
|
||||||
|
}
|
||||||
|
acbw.producersMu.Unlock()
|
||||||
|
}
|
||||||
|
return pData.producer, sync.OnceFunc(unref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acbw *acBalancerWrapper) closeProducers() {
|
||||||
|
acbw.producersMu.Lock()
|
||||||
|
defer acbw.producersMu.Unlock()
|
||||||
|
for pb, pData := range acbw.producers {
|
||||||
|
pData.refs = 0
|
||||||
|
pData.close()
|
||||||
|
delete(acbw.producers, pb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// healthProducerRegisterFn is a type alias for the health producer's function
|
||||||
|
// for registering listeners.
|
||||||
|
type healthProducerRegisterFn = func(context.Context, balancer.SubConn, string, func(balancer.SubConnState)) func()
|
||||||
|
|
||||||
|
// healthListenerRegFn returns a function to register a listener for health
|
||||||
|
// updates. If client side health checks are disabled, the registered listener
|
||||||
|
// will get a single READY (raw connectivity state) update.
|
||||||
|
//
|
||||||
|
// Client side health checking is enabled when all the following
|
||||||
|
// conditions are satisfied:
|
||||||
|
// 1. Health checking is not disabled using the dial option.
|
||||||
|
// 2. The health package is imported.
|
||||||
|
// 3. The health check config is present in the service config.
|
||||||
|
func (acbw *acBalancerWrapper) healthListenerRegFn() func(context.Context, func(balancer.SubConnState)) func() {
|
||||||
|
if acbw.ccb.cc.dopts.disableHealthCheck {
|
||||||
|
return noOpRegisterHealthListenerFn
|
||||||
|
}
|
||||||
|
cfg := acbw.ac.cc.healthCheckConfig()
|
||||||
|
if cfg == nil {
|
||||||
|
return noOpRegisterHealthListenerFn
|
||||||
|
}
|
||||||
|
regHealthLisFn := internal.RegisterClientHealthCheckListener
|
||||||
|
if regHealthLisFn == nil {
|
||||||
|
// The health package is not imported.
|
||||||
|
channelz.Error(logger, acbw.ac.channelz, "Health check is requested but health package is not imported.")
|
||||||
|
return noOpRegisterHealthListenerFn
|
||||||
|
}
|
||||||
|
return func(ctx context.Context, listener func(balancer.SubConnState)) func() {
|
||||||
|
return regHealthLisFn.(healthProducerRegisterFn)(ctx, acbw, cfg.ServiceName, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterHealthListener accepts a health listener from the LB policy. It sends
|
||||||
|
// updates to the health listener as long as the SubConn's connectivity state
|
||||||
|
// doesn't change and a new health listener is not registered. To invalidate
|
||||||
|
// the currently registered health listener, acbw updates the healthData. If a
|
||||||
|
// nil listener is registered, the active health listener is dropped.
|
||||||
|
func (acbw *acBalancerWrapper) RegisterHealthListener(listener func(balancer.SubConnState)) {
|
||||||
|
acbw.healthMu.Lock()
|
||||||
|
defer acbw.healthMu.Unlock()
|
||||||
|
acbw.healthData.closeHealthProducer()
|
||||||
|
// listeners should not be registered when the connectivity state
|
||||||
|
// isn't Ready. This may happen when the balancer registers a listener
|
||||||
|
// after the connectivityState is updated, but before it is notified
|
||||||
|
// of the update.
|
||||||
|
if acbw.healthData.connectivityState != connectivity.Ready {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Replace the health data to stop sending updates to any previously
|
||||||
|
// registered health listeners.
|
||||||
|
hd := newHealthData(connectivity.Ready)
|
||||||
|
acbw.healthData = hd
|
||||||
|
if listener == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
registerFn := acbw.healthListenerRegFn()
|
||||||
|
acbw.ccb.serializer.TrySchedule(func(ctx context.Context) {
|
||||||
|
if ctx.Err() != nil || acbw.ccb.balancer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Don't send updates if a new listener is registered.
|
||||||
|
acbw.healthMu.Lock()
|
||||||
|
defer acbw.healthMu.Unlock()
|
||||||
|
if acbw.healthData != hd {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Serialize the health updates from the health producer with
|
||||||
|
// other calls into the LB policy.
|
||||||
|
listenerWrapper := func(scs balancer.SubConnState) {
|
||||||
|
acbw.ccb.serializer.TrySchedule(func(ctx context.Context) {
|
||||||
|
if ctx.Err() != nil || acbw.ccb.balancer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acbw.healthMu.Lock()
|
||||||
|
defer acbw.healthMu.Unlock()
|
||||||
|
if acbw.healthData != hd {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listener(scs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
hd.closeHealthProducer = registerFn(ctx, listenerWrapper)
|
||||||
|
})
|
||||||
|
}
|
||||||
1004
vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go
generated
vendored
Normal file
1004
vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2014 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 grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Invoke sends the RPC request on the wire and returns after response is
|
||||||
|
// received. This is typically called by generated code.
|
||||||
|
//
|
||||||
|
// All errors returned by Invoke are compatible with the status package.
|
||||||
|
func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply any, opts ...CallOption) error {
|
||||||
|
// allow interceptor to see all applicable call options, which means those
|
||||||
|
// configured as defaults from dial option as well as per-call options
|
||||||
|
opts = combine(cc.dopts.callOptions, opts)
|
||||||
|
|
||||||
|
if cc.dopts.unaryInt != nil {
|
||||||
|
return cc.dopts.unaryInt(ctx, method, args, reply, cc, invoke, opts...)
|
||||||
|
}
|
||||||
|
return invoke(ctx, method, args, reply, cc, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func combine(o1 []CallOption, o2 []CallOption) []CallOption {
|
||||||
|
// we don't use append because o1 could have extra capacity whose
|
||||||
|
// elements would be overwritten, which could cause inadvertent
|
||||||
|
// sharing (and race conditions) between concurrent calls
|
||||||
|
if len(o1) == 0 {
|
||||||
|
return o2
|
||||||
|
} else if len(o2) == 0 {
|
||||||
|
return o1
|
||||||
|
}
|
||||||
|
ret := make([]CallOption, len(o1)+len(o2))
|
||||||
|
copy(ret, o1)
|
||||||
|
copy(ret[len(o1):], o2)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke sends the RPC request on the wire and returns after response is
|
||||||
|
// received. This is typically called by generated code.
|
||||||
|
//
|
||||||
|
// DEPRECATED: Use ClientConn.Invoke instead.
|
||||||
|
func Invoke(ctx context.Context, method string, args, reply any, cc *ClientConn, opts ...CallOption) error {
|
||||||
|
return cc.Invoke(ctx, method, args, reply, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var unaryStreamDesc = &StreamDesc{ServerStreams: false, ClientStreams: false}
|
||||||
|
|
||||||
|
func invoke(ctx context.Context, method string, req, reply any, cc *ClientConn, opts ...CallOption) error {
|
||||||
|
cs, err := newClientStream(ctx, unaryStreamDesc, cc, method, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cs.SendMsg(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return cs.RecvMsg(reply)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 channelz exports internals of the channelz implementation as required
|
||||||
|
// by other gRPC packages.
|
||||||
|
//
|
||||||
|
// The implementation of the channelz spec as defined in
|
||||||
|
// https://github.com/grpc/proposal/blob/master/A14-channelz.md, is provided by
|
||||||
|
// the `internal/channelz` package.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: All APIs in this package are experimental and may be removed in a
|
||||||
|
// later release.
|
||||||
|
package channelz
|
||||||
|
|
||||||
|
import "google.golang.org/grpc/internal/channelz"
|
||||||
|
|
||||||
|
// Identifier is an opaque identifier which uniquely identifies an entity in the
|
||||||
|
// channelz database.
|
||||||
|
type Identifier = channelz.Identifier
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2014 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 grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc/encoding"
|
||||||
|
_ "google.golang.org/grpc/encoding/proto" // to register the Codec for "proto"
|
||||||
|
"google.golang.org/grpc/mem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// baseCodec captures the new encoding.CodecV2 interface without the Name
|
||||||
|
// function, allowing it to be implemented by older Codec and encoding.Codec
|
||||||
|
// implementations. The omitted Name function is only needed for the register in
|
||||||
|
// the encoding package and is not part of the core functionality.
|
||||||
|
type baseCodec interface {
|
||||||
|
Marshal(v any) (mem.BufferSlice, error)
|
||||||
|
Unmarshal(data mem.BufferSlice, v any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCodec returns an encoding.CodecV2 for the codec of the given name (if
|
||||||
|
// registered). Initially checks the V2 registry with encoding.GetCodecV2 and
|
||||||
|
// returns the V2 codec if it is registered. Otherwise, it checks the V1 registry
|
||||||
|
// with encoding.GetCodec and if it is registered wraps it with newCodecV1Bridge
|
||||||
|
// to turn it into an encoding.CodecV2. Returns nil otherwise.
|
||||||
|
func getCodec(name string) encoding.CodecV2 {
|
||||||
|
if codecV1 := encoding.GetCodec(name); codecV1 != nil {
|
||||||
|
return newCodecV1Bridge(codecV1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoding.GetCodecV2(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCodecV0Bridge(c Codec) baseCodec {
|
||||||
|
return codecV0Bridge{codec: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCodecV1Bridge(c encoding.Codec) encoding.CodecV2 {
|
||||||
|
return codecV1Bridge{
|
||||||
|
codecV0Bridge: codecV0Bridge{codec: c},
|
||||||
|
name: c.Name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ baseCodec = codecV0Bridge{}
|
||||||
|
|
||||||
|
type codecV0Bridge struct {
|
||||||
|
codec interface {
|
||||||
|
Marshal(v any) ([]byte, error)
|
||||||
|
Unmarshal(data []byte, v any) error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c codecV0Bridge) Marshal(v any) (mem.BufferSlice, error) {
|
||||||
|
data, err := c.codec.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mem.BufferSlice{mem.SliceBuffer(data)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c codecV0Bridge) Unmarshal(data mem.BufferSlice, v any) (err error) {
|
||||||
|
return c.codec.Unmarshal(data.Materialize(), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ encoding.CodecV2 = codecV1Bridge{}
|
||||||
|
|
||||||
|
type codecV1Bridge struct {
|
||||||
|
codecV0Bridge
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c codecV1Bridge) Name() string {
|
||||||
|
return c.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Codec defines the interface gRPC uses to encode and decode messages.
|
||||||
|
// Note that implementations of this interface must be thread safe;
|
||||||
|
// a Codec's methods can be called from concurrent goroutines.
|
||||||
|
//
|
||||||
|
// Deprecated: use encoding.Codec instead.
|
||||||
|
type Codec interface {
|
||||||
|
// Marshal returns the wire format of v.
|
||||||
|
Marshal(v any) ([]byte, error)
|
||||||
|
// Unmarshal parses the wire format into v.
|
||||||
|
Unmarshal(data []byte, v any) error
|
||||||
|
// String returns the name of the Codec implementation. This is unused by
|
||||||
|
// gRPC.
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 codes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
internal.CanonicalString = canonicalString
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Code) String() string {
|
||||||
|
switch c {
|
||||||
|
case OK:
|
||||||
|
return "OK"
|
||||||
|
case Canceled:
|
||||||
|
return "Canceled"
|
||||||
|
case Unknown:
|
||||||
|
return "Unknown"
|
||||||
|
case InvalidArgument:
|
||||||
|
return "InvalidArgument"
|
||||||
|
case DeadlineExceeded:
|
||||||
|
return "DeadlineExceeded"
|
||||||
|
case NotFound:
|
||||||
|
return "NotFound"
|
||||||
|
case AlreadyExists:
|
||||||
|
return "AlreadyExists"
|
||||||
|
case PermissionDenied:
|
||||||
|
return "PermissionDenied"
|
||||||
|
case ResourceExhausted:
|
||||||
|
return "ResourceExhausted"
|
||||||
|
case FailedPrecondition:
|
||||||
|
return "FailedPrecondition"
|
||||||
|
case Aborted:
|
||||||
|
return "Aborted"
|
||||||
|
case OutOfRange:
|
||||||
|
return "OutOfRange"
|
||||||
|
case Unimplemented:
|
||||||
|
return "Unimplemented"
|
||||||
|
case Internal:
|
||||||
|
return "Internal"
|
||||||
|
case Unavailable:
|
||||||
|
return "Unavailable"
|
||||||
|
case DataLoss:
|
||||||
|
return "DataLoss"
|
||||||
|
case Unauthenticated:
|
||||||
|
return "Unauthenticated"
|
||||||
|
default:
|
||||||
|
return "Code(" + strconv.FormatInt(int64(c), 10) + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func canonicalString(c Code) string {
|
||||||
|
switch c {
|
||||||
|
case OK:
|
||||||
|
return "OK"
|
||||||
|
case Canceled:
|
||||||
|
return "CANCELLED"
|
||||||
|
case Unknown:
|
||||||
|
return "UNKNOWN"
|
||||||
|
case InvalidArgument:
|
||||||
|
return "INVALID_ARGUMENT"
|
||||||
|
case DeadlineExceeded:
|
||||||
|
return "DEADLINE_EXCEEDED"
|
||||||
|
case NotFound:
|
||||||
|
return "NOT_FOUND"
|
||||||
|
case AlreadyExists:
|
||||||
|
return "ALREADY_EXISTS"
|
||||||
|
case PermissionDenied:
|
||||||
|
return "PERMISSION_DENIED"
|
||||||
|
case ResourceExhausted:
|
||||||
|
return "RESOURCE_EXHAUSTED"
|
||||||
|
case FailedPrecondition:
|
||||||
|
return "FAILED_PRECONDITION"
|
||||||
|
case Aborted:
|
||||||
|
return "ABORTED"
|
||||||
|
case OutOfRange:
|
||||||
|
return "OUT_OF_RANGE"
|
||||||
|
case Unimplemented:
|
||||||
|
return "UNIMPLEMENTED"
|
||||||
|
case Internal:
|
||||||
|
return "INTERNAL"
|
||||||
|
case Unavailable:
|
||||||
|
return "UNAVAILABLE"
|
||||||
|
case DataLoss:
|
||||||
|
return "DATA_LOSS"
|
||||||
|
case Unauthenticated:
|
||||||
|
return "UNAUTHENTICATED"
|
||||||
|
default:
|
||||||
|
return "CODE(" + strconv.FormatInt(int64(c), 10) + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2014 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 codes defines the canonical error codes used by gRPC. It is
|
||||||
|
// consistent across various languages.
|
||||||
|
package codes // import "google.golang.org/grpc/codes"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Code is a status code defined according to the [gRPC documentation].
|
||||||
|
//
|
||||||
|
// Only the codes defined as consts in this package are valid codes. Do not use
|
||||||
|
// other code values. Behavior of other codes is implementation-specific and
|
||||||
|
// interoperability between implementations is not guaranteed.
|
||||||
|
//
|
||||||
|
// [gRPC documentation]: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
|
||||||
|
type Code uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// OK is returned on success.
|
||||||
|
OK Code = 0
|
||||||
|
|
||||||
|
// Canceled indicates the operation was canceled (typically by the caller).
|
||||||
|
//
|
||||||
|
// The gRPC framework will generate this error code when cancellation
|
||||||
|
// is requested.
|
||||||
|
Canceled Code = 1
|
||||||
|
|
||||||
|
// Unknown error. An example of where this error may be returned is
|
||||||
|
// if a Status value received from another address space belongs to
|
||||||
|
// an error-space that is not known in this address space. Also
|
||||||
|
// errors raised by APIs that do not return enough error information
|
||||||
|
// may be converted to this error.
|
||||||
|
//
|
||||||
|
// The gRPC framework will generate this error code in the above two
|
||||||
|
// mentioned cases.
|
||||||
|
Unknown Code = 2
|
||||||
|
|
||||||
|
// InvalidArgument indicates client specified an invalid argument.
|
||||||
|
// Note that this differs from FailedPrecondition. It indicates arguments
|
||||||
|
// that are problematic regardless of the state of the system
|
||||||
|
// (e.g., a malformed file name).
|
||||||
|
//
|
||||||
|
// This error code will not be generated by the gRPC framework.
|
||||||
|
InvalidArgument Code = 3
|
||||||
|
|
||||||
|
// DeadlineExceeded means operation expired before completion.
|
||||||
|
// For operations that change the state of the system, this error may be
|
||||||
|
// returned even if the operation has completed successfully. For
|
||||||
|
// example, a successful response from a server could have been delayed
|
||||||
|
// long enough for the deadline to expire.
|
||||||
|
//
|
||||||
|
// The gRPC framework will generate this error code when the deadline is
|
||||||
|
// exceeded.
|
||||||
|
DeadlineExceeded Code = 4
|
||||||
|
|
||||||
|
// NotFound means some requested entity (e.g., file or directory) was
|
||||||
|
// not found.
|
||||||
|
//
|
||||||
|
// This error code will not be generated by the gRPC framework.
|
||||||
|
NotFound Code = 5
|
||||||
|
|
||||||
|
// AlreadyExists means an attempt to create an entity failed because one
|
||||||
|
// already exists.
|
||||||
|
//
|
||||||
|
// This error code will not be generated by the gRPC framework.
|
||||||
|
AlreadyExists Code = 6
|
||||||
|
|
||||||
|
// PermissionDenied indicates the caller does not have permission to
|
||||||
|
// execute the specified operation. It must not be used for rejections
|
||||||
|
// caused by exhausting some resource (use ResourceExhausted
|
||||||
|
// instead for those errors). It must not be
|
||||||
|
// used if the caller cannot be identified (use Unauthenticated
|
||||||
|
// instead for those errors).
|
||||||
|
//
|
||||||
|
// This error code will not be generated by the gRPC core framework,
|
||||||
|
// but expect authentication middleware to use it.
|
||||||
|
PermissionDenied Code = 7
|
||||||
|
|
||||||
|
// ResourceExhausted indicates some resource has been exhausted, perhaps
|
||||||
|
// a per-user quota, or perhaps the entire file system is out of space.
|
||||||
|
//
|
||||||
|
// This error code will be generated by the gRPC framework in
|
||||||
|
// out-of-memory and server overload situations, or when a message is
|
||||||
|
// larger than the configured maximum size.
|
||||||
|
ResourceExhausted Code = 8
|
||||||
|
|
||||||
|
// FailedPrecondition indicates operation was rejected because the
|
||||||
|
// system is not in a state required for the operation's execution.
|
||||||
|
// For example, directory to be deleted may be non-empty, an rmdir
|
||||||
|
// operation is applied to a non-directory, etc.
|
||||||
|
//
|
||||||
|
// A litmus test that may help a service implementor in deciding
|
||||||
|
// between FailedPrecondition, Aborted, and Unavailable:
|
||||||
|
// (a) Use Unavailable if the client can retry just the failing call.
|
||||||
|
// (b) Use Aborted if the client should retry at a higher-level
|
||||||
|
// (e.g., restarting a read-modify-write sequence).
|
||||||
|
// (c) Use FailedPrecondition if the client should not retry until
|
||||||
|
// the system state has been explicitly fixed. E.g., if an "rmdir"
|
||||||
|
// fails because the directory is non-empty, FailedPrecondition
|
||||||
|
// should be returned since the client should not retry unless
|
||||||
|
// they have first fixed up the directory by deleting files from it.
|
||||||
|
// (d) Use FailedPrecondition if the client performs conditional
|
||||||
|
// REST Get/Update/Delete on a resource and the resource on the
|
||||||
|
// server does not match the condition. E.g., conflicting
|
||||||
|
// read-modify-write on the same resource.
|
||||||
|
//
|
||||||
|
// This error code will not be generated by the gRPC framework.
|
||||||
|
FailedPrecondition Code = 9
|
||||||
|
|
||||||
|
// Aborted indicates the operation was aborted, typically due to a
|
||||||
|
// concurrency issue like sequencer check failures, transaction aborts,
|
||||||
|
// etc.
|
||||||
|
//
|
||||||
|
// See litmus test above for deciding between FailedPrecondition,
|
||||||
|
// Aborted, and Unavailable.
|
||||||
|
//
|
||||||
|
// This error code will not be generated by the gRPC framework.
|
||||||
|
Aborted Code = 10
|
||||||
|
|
||||||
|
// OutOfRange means operation was attempted past the valid range.
|
||||||
|
// E.g., seeking or reading past end of file.
|
||||||
|
//
|
||||||
|
// Unlike InvalidArgument, this error indicates a problem that may
|
||||||
|
// be fixed if the system state changes. For example, a 32-bit file
|
||||||
|
// system will generate InvalidArgument if asked to read at an
|
||||||
|
// offset that is not in the range [0,2^32-1], but it will generate
|
||||||
|
// OutOfRange if asked to read from an offset past the current
|
||||||
|
// file size.
|
||||||
|
//
|
||||||
|
// There is a fair bit of overlap between FailedPrecondition and
|
||||||
|
// OutOfRange. We recommend using OutOfRange (the more specific
|
||||||
|
// error) when it applies so that callers who are iterating through
|
||||||
|
// a space can easily look for an OutOfRange error to detect when
|
||||||
|
// they are done.
|
||||||
|
//
|
||||||
|
// This error code will not be generated by the gRPC framework.
|
||||||
|
OutOfRange Code = 11
|
||||||
|
|
||||||
|
// Unimplemented indicates operation is not implemented or not
|
||||||
|
// supported/enabled in this service.
|
||||||
|
//
|
||||||
|
// This error code will be generated by the gRPC framework. Most
|
||||||
|
// commonly, you will see this error code when a method implementation
|
||||||
|
// is missing on the server. It can also be generated for unknown
|
||||||
|
// compression algorithms or a disagreement as to whether an RPC should
|
||||||
|
// be streaming.
|
||||||
|
Unimplemented Code = 12
|
||||||
|
|
||||||
|
// Internal errors. Means some invariants expected by underlying
|
||||||
|
// system has been broken. If you see one of these errors,
|
||||||
|
// something is very broken.
|
||||||
|
//
|
||||||
|
// This error code will be generated by the gRPC framework in several
|
||||||
|
// internal error conditions.
|
||||||
|
Internal Code = 13
|
||||||
|
|
||||||
|
// Unavailable indicates the service is currently unavailable.
|
||||||
|
// This is a most likely a transient condition and may be corrected
|
||||||
|
// by retrying with a backoff. Note that it is not always safe to retry
|
||||||
|
// non-idempotent operations.
|
||||||
|
//
|
||||||
|
// See litmus test above for deciding between FailedPrecondition,
|
||||||
|
// Aborted, and Unavailable.
|
||||||
|
//
|
||||||
|
// This error code will be generated by the gRPC framework during
|
||||||
|
// abrupt shutdown of a server process or network connection.
|
||||||
|
Unavailable Code = 14
|
||||||
|
|
||||||
|
// DataLoss indicates unrecoverable data loss or corruption.
|
||||||
|
//
|
||||||
|
// This error code will not be generated by the gRPC framework.
|
||||||
|
DataLoss Code = 15
|
||||||
|
|
||||||
|
// Unauthenticated indicates the request does not have valid
|
||||||
|
// authentication credentials for the operation.
|
||||||
|
//
|
||||||
|
// The gRPC framework will generate this error code when the
|
||||||
|
// authentication metadata is invalid or a Credentials callback fails,
|
||||||
|
// but also expect authentication middleware to generate it.
|
||||||
|
Unauthenticated Code = 16
|
||||||
|
|
||||||
|
_maxCode = 17
|
||||||
|
)
|
||||||
|
|
||||||
|
var strToCode = map[string]Code{
|
||||||
|
`"OK"`: OK,
|
||||||
|
`"CANCELLED"`:/* [sic] */ Canceled,
|
||||||
|
`"UNKNOWN"`: Unknown,
|
||||||
|
`"INVALID_ARGUMENT"`: InvalidArgument,
|
||||||
|
`"DEADLINE_EXCEEDED"`: DeadlineExceeded,
|
||||||
|
`"NOT_FOUND"`: NotFound,
|
||||||
|
`"ALREADY_EXISTS"`: AlreadyExists,
|
||||||
|
`"PERMISSION_DENIED"`: PermissionDenied,
|
||||||
|
`"RESOURCE_EXHAUSTED"`: ResourceExhausted,
|
||||||
|
`"FAILED_PRECONDITION"`: FailedPrecondition,
|
||||||
|
`"ABORTED"`: Aborted,
|
||||||
|
`"OUT_OF_RANGE"`: OutOfRange,
|
||||||
|
`"UNIMPLEMENTED"`: Unimplemented,
|
||||||
|
`"INTERNAL"`: Internal,
|
||||||
|
`"UNAVAILABLE"`: Unavailable,
|
||||||
|
`"DATA_LOSS"`: DataLoss,
|
||||||
|
`"UNAUTHENTICATED"`: Unauthenticated,
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals b into the Code.
|
||||||
|
func (c *Code) UnmarshalJSON(b []byte) error {
|
||||||
|
// From json.Unmarshaler: By convention, to approximate the behavior of
|
||||||
|
// Unmarshal itself, Unmarshalers implement UnmarshalJSON([]byte("null")) as
|
||||||
|
// a no-op.
|
||||||
|
if string(b) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c == nil {
|
||||||
|
return fmt.Errorf("nil receiver passed to UnmarshalJSON")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ci, err := strconv.ParseUint(string(b), 10, 32); err == nil {
|
||||||
|
if ci >= _maxCode {
|
||||||
|
return fmt.Errorf("invalid code: %d", ci)
|
||||||
|
}
|
||||||
|
|
||||||
|
*c = Code(ci)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if jc, ok := strToCode[string(b)]; ok {
|
||||||
|
*c = jc
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid code: %q", string(b))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 connectivity defines connectivity semantics.
|
||||||
|
// For details, see https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md.
|
||||||
|
package connectivity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = grpclog.Component("core")
|
||||||
|
|
||||||
|
// State indicates the state of connectivity.
|
||||||
|
// It can be the state of a ClientConn or SubConn.
|
||||||
|
type State int
|
||||||
|
|
||||||
|
func (s State) String() string {
|
||||||
|
switch s {
|
||||||
|
case Idle:
|
||||||
|
return "IDLE"
|
||||||
|
case Connecting:
|
||||||
|
return "CONNECTING"
|
||||||
|
case Ready:
|
||||||
|
return "READY"
|
||||||
|
case TransientFailure:
|
||||||
|
return "TRANSIENT_FAILURE"
|
||||||
|
case Shutdown:
|
||||||
|
return "SHUTDOWN"
|
||||||
|
default:
|
||||||
|
logger.Errorf("unknown connectivity state: %d", s)
|
||||||
|
return "INVALID_STATE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Idle indicates the ClientConn is idle.
|
||||||
|
Idle State = iota
|
||||||
|
// Connecting indicates the ClientConn is connecting.
|
||||||
|
Connecting
|
||||||
|
// Ready indicates the ClientConn is ready for work.
|
||||||
|
Ready
|
||||||
|
// TransientFailure indicates the ClientConn has seen a failure but expects to recover.
|
||||||
|
TransientFailure
|
||||||
|
// Shutdown indicates the ClientConn has started shutting down.
|
||||||
|
Shutdown
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServingMode indicates the current mode of operation of the server.
|
||||||
|
//
|
||||||
|
// Only xDS enabled gRPC servers currently report their serving mode.
|
||||||
|
type ServingMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ServingModeStarting indicates that the server is starting up.
|
||||||
|
ServingModeStarting ServingMode = iota
|
||||||
|
// ServingModeServing indicates that the server contains all required
|
||||||
|
// configuration and is serving RPCs.
|
||||||
|
ServingModeServing
|
||||||
|
// ServingModeNotServing indicates that the server is not accepting new
|
||||||
|
// connections. Existing connections will be closed gracefully, allowing
|
||||||
|
// in-progress RPCs to complete. A server enters this mode when it does not
|
||||||
|
// contain the required configuration to serve RPCs.
|
||||||
|
ServingModeNotServing
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s ServingMode) String() string {
|
||||||
|
switch s {
|
||||||
|
case ServingModeStarting:
|
||||||
|
return "STARTING"
|
||||||
|
case ServingModeServing:
|
||||||
|
return "SERVING"
|
||||||
|
case ServingModeNotServing:
|
||||||
|
return "NOT_SERVING"
|
||||||
|
default:
|
||||||
|
logger.Errorf("unknown serving mode: %d", s)
|
||||||
|
return "INVALID_MODE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,337 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2014 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 credentials implements various credentials supported by gRPC library,
|
||||||
|
// which encapsulate all the state needed by a client to authenticate with a
|
||||||
|
// server and make various assertions, e.g., about the client's identity, role,
|
||||||
|
// or whether it is authorized to make a particular call.
|
||||||
|
package credentials // import "google.golang.org/grpc/credentials"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/attributes"
|
||||||
|
icredentials "google.golang.org/grpc/internal/credentials"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PerRPCCredentials defines the common interface for the credentials which need to
|
||||||
|
// attach security information to every RPC (e.g., oauth2).
|
||||||
|
type PerRPCCredentials interface {
|
||||||
|
// GetRequestMetadata gets the current request metadata, refreshing tokens
|
||||||
|
// if required. This should be called by the transport layer on each
|
||||||
|
// request, and the data should be populated in headers or other
|
||||||
|
// context. If a status code is returned, it will be used as the status for
|
||||||
|
// the RPC (restricted to an allowable set of codes as defined by gRFC
|
||||||
|
// A54). uri is the URI of the entry point for the request. When supported
|
||||||
|
// by the underlying implementation, ctx can be used for timeout and
|
||||||
|
// cancellation. Additionally, RequestInfo data will be available via ctx
|
||||||
|
// to this call.
|
||||||
|
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
|
||||||
|
// RequireTransportSecurity indicates whether the credentials requires
|
||||||
|
// transport security.
|
||||||
|
RequireTransportSecurity() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecurityLevel defines the protection level on an established connection.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
type SecurityLevel int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// InvalidSecurityLevel indicates an invalid security level.
|
||||||
|
// The zero SecurityLevel value is invalid for backward compatibility.
|
||||||
|
InvalidSecurityLevel SecurityLevel = iota
|
||||||
|
// NoSecurity indicates a connection is insecure.
|
||||||
|
NoSecurity
|
||||||
|
// IntegrityOnly indicates a connection only provides integrity protection.
|
||||||
|
IntegrityOnly
|
||||||
|
// PrivacyAndIntegrity indicates a connection provides both privacy and integrity protection.
|
||||||
|
PrivacyAndIntegrity
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns SecurityLevel in a string format.
|
||||||
|
func (s SecurityLevel) String() string {
|
||||||
|
switch s {
|
||||||
|
case NoSecurity:
|
||||||
|
return "NoSecurity"
|
||||||
|
case IntegrityOnly:
|
||||||
|
return "IntegrityOnly"
|
||||||
|
case PrivacyAndIntegrity:
|
||||||
|
return "PrivacyAndIntegrity"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("invalid SecurityLevel: %v", int(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommonAuthInfo contains authenticated information common to AuthInfo implementations.
|
||||||
|
// It should be embedded in a struct implementing AuthInfo to provide additional information
|
||||||
|
// about the credentials.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
type CommonAuthInfo struct {
|
||||||
|
SecurityLevel SecurityLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommonAuthInfo returns the pointer to CommonAuthInfo struct.
|
||||||
|
func (c CommonAuthInfo) GetCommonAuthInfo() CommonAuthInfo {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProtocolInfo provides static information regarding transport credentials.
|
||||||
|
type ProtocolInfo struct {
|
||||||
|
// ProtocolVersion is the gRPC wire protocol version.
|
||||||
|
//
|
||||||
|
// Deprecated: this is unused by gRPC.
|
||||||
|
ProtocolVersion string
|
||||||
|
// SecurityProtocol is the security protocol in use.
|
||||||
|
SecurityProtocol string
|
||||||
|
// SecurityVersion is the security protocol version. It is a static version string from the
|
||||||
|
// credentials, not a value that reflects per-connection protocol negotiation. To retrieve
|
||||||
|
// details about the credentials used for a connection, use the Peer's AuthInfo field instead.
|
||||||
|
//
|
||||||
|
// Deprecated: please use Peer.AuthInfo.
|
||||||
|
SecurityVersion string
|
||||||
|
// ServerName is the user-configured server name. If set, this overrides
|
||||||
|
// the default :authority header used for all RPCs on the channel using the
|
||||||
|
// containing credentials, unless grpc.WithAuthority is set on the channel,
|
||||||
|
// in which case that setting will take precedence.
|
||||||
|
//
|
||||||
|
// This must be a valid `:authority` header according to
|
||||||
|
// [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2).
|
||||||
|
//
|
||||||
|
// Deprecated: Users should use grpc.WithAuthority to override the authority
|
||||||
|
// on a channel instead of configuring the credentials.
|
||||||
|
ServerName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthInfo defines the common interface for the auth information the users are interested in.
|
||||||
|
// A struct that implements AuthInfo should embed CommonAuthInfo by including additional
|
||||||
|
// information about the credentials in it.
|
||||||
|
type AuthInfo interface {
|
||||||
|
AuthType() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorityValidator validates the authority used to override the `:authority`
|
||||||
|
// header. This is an optional interface that implementations of AuthInfo can
|
||||||
|
// implement if they support per-RPC authority overrides. It is invoked when the
|
||||||
|
// application attempts to override the HTTP/2 `:authority` header using the
|
||||||
|
// CallAuthority call option.
|
||||||
|
type AuthorityValidator interface {
|
||||||
|
// ValidateAuthority checks the authority value used to override the
|
||||||
|
// `:authority` header. The authority parameter is the override value
|
||||||
|
// provided by the application via the CallAuthority option. This value
|
||||||
|
// typically corresponds to the server hostname or endpoint the RPC is
|
||||||
|
// targeting. It returns non-nil error if the validation fails.
|
||||||
|
ValidateAuthority(authority string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrConnDispatched indicates that rawConn has been dispatched out of gRPC
|
||||||
|
// and the caller should not close rawConn.
|
||||||
|
var ErrConnDispatched = errors.New("credentials: rawConn is dispatched out of gRPC")
|
||||||
|
|
||||||
|
// TransportCredentials defines the common interface for all the live gRPC wire
|
||||||
|
// protocols and supported transport security protocols (e.g., TLS, SSL).
|
||||||
|
type TransportCredentials interface {
|
||||||
|
// ClientHandshake does the authentication handshake specified by the
|
||||||
|
// corresponding authentication protocol on rawConn for clients. It returns
|
||||||
|
// the authenticated connection and the corresponding auth information
|
||||||
|
// about the connection. The auth information should embed CommonAuthInfo
|
||||||
|
// to return additional information about the credentials. Implementations
|
||||||
|
// must use the provided context to implement timely cancellation. gRPC
|
||||||
|
// will try to reconnect if the error returned is a temporary error
|
||||||
|
// (io.EOF, context.DeadlineExceeded or err.Temporary() == true). If the
|
||||||
|
// returned error is a wrapper error, implementations should make sure that
|
||||||
|
// the error implements Temporary() to have the correct retry behaviors.
|
||||||
|
// Additionally, ClientHandshakeInfo data will be available via the context
|
||||||
|
// passed to this call.
|
||||||
|
//
|
||||||
|
// The second argument to this method is the `:authority` header value used
|
||||||
|
// while creating new streams on this connection after authentication
|
||||||
|
// succeeds. Implementations must use this as the server name during the
|
||||||
|
// authentication handshake.
|
||||||
|
//
|
||||||
|
// If the returned net.Conn is closed, it MUST close the net.Conn provided.
|
||||||
|
ClientHandshake(context.Context, string, net.Conn) (net.Conn, AuthInfo, error)
|
||||||
|
// ServerHandshake does the authentication handshake for servers. It returns
|
||||||
|
// the authenticated connection and the corresponding auth information about
|
||||||
|
// the connection. The auth information should embed CommonAuthInfo to return additional information
|
||||||
|
// about the credentials.
|
||||||
|
//
|
||||||
|
// If the returned net.Conn is closed, it MUST close the net.Conn provided.
|
||||||
|
ServerHandshake(net.Conn) (net.Conn, AuthInfo, error)
|
||||||
|
// Info provides the ProtocolInfo of this TransportCredentials.
|
||||||
|
Info() ProtocolInfo
|
||||||
|
// Clone makes a copy of this TransportCredentials.
|
||||||
|
Clone() TransportCredentials
|
||||||
|
// OverrideServerName specifies the value used for the following:
|
||||||
|
//
|
||||||
|
// - verifying the hostname on the returned certificates
|
||||||
|
// - as SNI in the client's handshake to support virtual hosting
|
||||||
|
// - as the value for `:authority` header at stream creation time
|
||||||
|
//
|
||||||
|
// The provided string should be a valid `:authority` header according to
|
||||||
|
// [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2).
|
||||||
|
//
|
||||||
|
// Deprecated: this method is unused by gRPC. Users should use
|
||||||
|
// grpc.WithAuthority to override the authority on a channel instead of
|
||||||
|
// configuring the credentials.
|
||||||
|
OverrideServerName(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bundle is a combination of TransportCredentials and PerRPCCredentials.
|
||||||
|
//
|
||||||
|
// It also contains a mode switching method, so it can be used as a combination
|
||||||
|
// of different credential policies.
|
||||||
|
//
|
||||||
|
// Bundle cannot be used together with individual TransportCredentials.
|
||||||
|
// PerRPCCredentials from Bundle will be appended to other PerRPCCredentials.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
type Bundle interface {
|
||||||
|
// TransportCredentials returns the transport credentials from the Bundle.
|
||||||
|
//
|
||||||
|
// Implementations must return non-nil transport credentials. If transport
|
||||||
|
// security is not needed by the Bundle, implementations may choose to
|
||||||
|
// return insecure.NewCredentials().
|
||||||
|
TransportCredentials() TransportCredentials
|
||||||
|
|
||||||
|
// PerRPCCredentials returns the per-RPC credentials from the Bundle.
|
||||||
|
//
|
||||||
|
// May be nil if per-RPC credentials are not needed.
|
||||||
|
PerRPCCredentials() PerRPCCredentials
|
||||||
|
|
||||||
|
// NewWithMode should make a copy of Bundle, and switch mode. Modifying the
|
||||||
|
// existing Bundle may cause races.
|
||||||
|
//
|
||||||
|
// NewWithMode returns nil if the requested mode is not supported.
|
||||||
|
NewWithMode(mode string) (Bundle, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestInfo contains request data attached to the context passed to GetRequestMetadata calls.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
type RequestInfo struct {
|
||||||
|
// The method passed to Invoke or NewStream for this RPC. (For proto methods, this has the format "/some.Service/Method")
|
||||||
|
Method string
|
||||||
|
// AuthInfo contains the information from a security handshake (TransportCredentials.ClientHandshake, TransportCredentials.ServerHandshake)
|
||||||
|
AuthInfo AuthInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestInfoKey is a struct to be used as the key to store RequestInfo in a
|
||||||
|
// context.
|
||||||
|
type requestInfoKey struct{}
|
||||||
|
|
||||||
|
// RequestInfoFromContext extracts the RequestInfo from the context if it exists.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
func RequestInfoFromContext(ctx context.Context) (ri RequestInfo, ok bool) {
|
||||||
|
ri, ok = ctx.Value(requestInfoKey{}).(RequestInfo)
|
||||||
|
return ri, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContextWithRequestInfo creates a new context from ctx and attaches ri to it.
|
||||||
|
//
|
||||||
|
// This RequestInfo will be accessible via RequestInfoFromContext.
|
||||||
|
//
|
||||||
|
// Intended to be used from tests for PerRPCCredentials implementations (that
|
||||||
|
// often need to check connection's SecurityLevel). Should not be used from
|
||||||
|
// non-test code: the gRPC client already prepares a context with the correct
|
||||||
|
// RequestInfo attached when calling PerRPCCredentials.GetRequestMetadata.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
func NewContextWithRequestInfo(ctx context.Context, ri RequestInfo) context.Context {
|
||||||
|
return context.WithValue(ctx, requestInfoKey{}, ri)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientHandshakeInfo holds data to be passed to ClientHandshake. This makes
|
||||||
|
// it possible to pass arbitrary data to the handshaker from gRPC, resolver,
|
||||||
|
// balancer etc. Individual credential implementations control the actual
|
||||||
|
// format of the data that they are willing to receive.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
type ClientHandshakeInfo struct {
|
||||||
|
// Attributes contains the attributes for the address. It could be provided
|
||||||
|
// by the gRPC, resolver, balancer etc.
|
||||||
|
Attributes *attributes.Attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientHandshakeInfoFromContext returns the ClientHandshakeInfo struct stored
|
||||||
|
// in ctx.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
func ClientHandshakeInfoFromContext(ctx context.Context) ClientHandshakeInfo {
|
||||||
|
chi, _ := icredentials.ClientHandshakeInfoFromContext(ctx).(ClientHandshakeInfo)
|
||||||
|
return chi
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckSecurityLevel checks if a connection's security level is greater than or equal to the specified one.
|
||||||
|
// It returns success if 1) the condition is satisfied or 2) AuthInfo struct does not implement GetCommonAuthInfo() method
|
||||||
|
// or 3) CommonAuthInfo.SecurityLevel has an invalid zero value. For 2) and 3), it is for the purpose of backward-compatibility.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
func CheckSecurityLevel(ai AuthInfo, level SecurityLevel) error {
|
||||||
|
type internalInfo interface {
|
||||||
|
GetCommonAuthInfo() CommonAuthInfo
|
||||||
|
}
|
||||||
|
if ai == nil {
|
||||||
|
return errors.New("AuthInfo is nil")
|
||||||
|
}
|
||||||
|
if ci, ok := ai.(internalInfo); ok {
|
||||||
|
// CommonAuthInfo.SecurityLevel has an invalid value.
|
||||||
|
if ci.GetCommonAuthInfo().SecurityLevel == InvalidSecurityLevel {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ci.GetCommonAuthInfo().SecurityLevel < level {
|
||||||
|
return fmt.Errorf("requires SecurityLevel %v; connection has %v", level, ci.GetCommonAuthInfo().SecurityLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The condition is satisfied or AuthInfo struct does not implement GetCommonAuthInfo() method.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelzSecurityInfo defines the interface that security protocols should implement
|
||||||
|
// in order to provide security info to channelz.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
type ChannelzSecurityInfo interface {
|
||||||
|
GetSecurityValue() ChannelzSecurityValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelzSecurityValue defines the interface that GetSecurityValue() return value
|
||||||
|
// should satisfy. This interface should only be satisfied by *TLSChannelzSecurityValue
|
||||||
|
// and *OtherChannelzSecurityValue.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
type ChannelzSecurityValue interface {
|
||||||
|
isChannelzSecurityValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OtherChannelzSecurityValue defines the struct that non-TLS protocol should return
|
||||||
|
// from GetSecurityValue(), which contains protocol specific security info. Note
|
||||||
|
// the Value field will be sent to users of channelz requesting channel info, and
|
||||||
|
// thus sensitive info should better be avoided.
|
||||||
|
//
|
||||||
|
// This API is experimental.
|
||||||
|
type OtherChannelzSecurityValue struct {
|
||||||
|
ChannelzSecurityValue
|
||||||
|
Name string
|
||||||
|
Value proto.Message
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 insecure provides an implementation of the
|
||||||
|
// credentials.TransportCredentials interface which disables transport security.
|
||||||
|
package insecure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCredentials returns a credentials which disables transport security.
|
||||||
|
//
|
||||||
|
// Note that using this credentials with per-RPC credentials which require
|
||||||
|
// transport security is incompatible and will cause RPCs to fail.
|
||||||
|
func NewCredentials() credentials.TransportCredentials {
|
||||||
|
return insecureTC{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insecureTC implements the insecure transport credentials. The handshake
|
||||||
|
// methods simply return the passed in net.Conn and set the security level to
|
||||||
|
// NoSecurity.
|
||||||
|
type insecureTC struct{}
|
||||||
|
|
||||||
|
func (insecureTC) ClientHandshake(_ context.Context, _ string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||||
|
return conn, info{credentials.CommonAuthInfo{SecurityLevel: credentials.NoSecurity}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (insecureTC) ServerHandshake(conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||||
|
return conn, info{credentials.CommonAuthInfo{SecurityLevel: credentials.NoSecurity}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (insecureTC) Info() credentials.ProtocolInfo {
|
||||||
|
return credentials.ProtocolInfo{SecurityProtocol: "insecure"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (insecureTC) Clone() credentials.TransportCredentials {
|
||||||
|
return insecureTC{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (insecureTC) OverrideServerName(string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// info contains the auth information for an insecure connection.
|
||||||
|
// It implements the AuthInfo interface.
|
||||||
|
type info struct {
|
||||||
|
credentials.CommonAuthInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthType returns the type of info as a string.
|
||||||
|
func (info) AuthType() string {
|
||||||
|
return "insecure"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAuthority allows any value to be overridden for the :authority
|
||||||
|
// header.
|
||||||
|
func (info) ValidateAuthority(string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// insecureBundle implements an insecure bundle.
|
||||||
|
// An insecure bundle provides a thin wrapper around insecureTC to support
|
||||||
|
// the credentials.Bundle interface.
|
||||||
|
type insecureBundle struct{}
|
||||||
|
|
||||||
|
// NewBundle returns a bundle with disabled transport security and no per rpc credential.
|
||||||
|
func NewBundle() credentials.Bundle {
|
||||||
|
return insecureBundle{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithMode returns a new insecure Bundle. The mode is ignored.
|
||||||
|
func (insecureBundle) NewWithMode(string) (credentials.Bundle, error) {
|
||||||
|
return insecureBundle{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerRPCCredentials returns an nil implementation as insecure
|
||||||
|
// bundle does not support a per rpc credential.
|
||||||
|
func (insecureBundle) PerRPCCredentials() credentials.PerRPCCredentials {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransportCredentials returns the underlying insecure transport credential.
|
||||||
|
func (insecureBundle) TransportCredentials() credentials.TransportCredentials {
|
||||||
|
return NewCredentials()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,324 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2014 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 credentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
credinternal "google.golang.org/grpc/internal/credentials"
|
||||||
|
"google.golang.org/grpc/internal/envconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
const alpnFailureHelpMessage = "If you upgraded from a grpc-go version earlier than 1.67, your TLS connections may have stopped working due to ALPN enforcement. For more details, see: https://github.com/grpc/grpc-go/issues/434"
|
||||||
|
|
||||||
|
var logger = grpclog.Component("credentials")
|
||||||
|
|
||||||
|
// TLSInfo contains the auth information for a TLS authenticated connection.
|
||||||
|
// It implements the AuthInfo interface.
|
||||||
|
type TLSInfo struct {
|
||||||
|
State tls.ConnectionState
|
||||||
|
CommonAuthInfo
|
||||||
|
// This API is experimental.
|
||||||
|
SPIFFEID *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthType returns the type of TLSInfo as a string.
|
||||||
|
func (t TLSInfo) AuthType() string {
|
||||||
|
return "tls"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAuthority validates the provided authority being used to override the
|
||||||
|
// :authority header by verifying it against the peer certificates. It returns a
|
||||||
|
// non-nil error if the validation fails.
|
||||||
|
func (t TLSInfo) ValidateAuthority(authority string) error {
|
||||||
|
var errs []error
|
||||||
|
host, _, err := net.SplitHostPort(authority)
|
||||||
|
if err != nil {
|
||||||
|
host = authority
|
||||||
|
}
|
||||||
|
for _, cert := range t.State.PeerCertificates {
|
||||||
|
var err error
|
||||||
|
if err = cert.VerifyHostname(host); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("credentials: invalid authority %q: %v", authority, errors.Join(errs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// cipherSuiteLookup returns the string version of a TLS cipher suite ID.
|
||||||
|
func cipherSuiteLookup(cipherSuiteID uint16) string {
|
||||||
|
for _, s := range tls.CipherSuites() {
|
||||||
|
if s.ID == cipherSuiteID {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range tls.InsecureCipherSuites() {
|
||||||
|
if s.ID == cipherSuiteID {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("unknown ID: %v", cipherSuiteID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSecurityValue returns security info requested by channelz.
|
||||||
|
func (t TLSInfo) GetSecurityValue() ChannelzSecurityValue {
|
||||||
|
v := &TLSChannelzSecurityValue{
|
||||||
|
StandardName: cipherSuiteLookup(t.State.CipherSuite),
|
||||||
|
}
|
||||||
|
// Currently there's no way to get LocalCertificate info from tls package.
|
||||||
|
if len(t.State.PeerCertificates) > 0 {
|
||||||
|
v.RemoteCertificate = t.State.PeerCertificates[0].Raw
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// tlsCreds is the credentials required for authenticating a connection using TLS.
|
||||||
|
type tlsCreds struct {
|
||||||
|
// TLS configuration
|
||||||
|
config *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c tlsCreds) Info() ProtocolInfo {
|
||||||
|
return ProtocolInfo{
|
||||||
|
SecurityProtocol: "tls",
|
||||||
|
SecurityVersion: "1.2",
|
||||||
|
ServerName: c.config.ServerName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsCreds) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (_ net.Conn, _ AuthInfo, err error) {
|
||||||
|
// use local cfg to avoid clobbering ServerName if using multiple endpoints
|
||||||
|
cfg := credinternal.CloneTLSConfig(c.config)
|
||||||
|
|
||||||
|
serverName, _, err := net.SplitHostPort(authority)
|
||||||
|
if err != nil {
|
||||||
|
// If the authority had no host port or if the authority cannot be parsed, use it as-is.
|
||||||
|
serverName = authority
|
||||||
|
}
|
||||||
|
cfg.ServerName = serverName
|
||||||
|
|
||||||
|
conn := tls.Client(rawConn, cfg)
|
||||||
|
errChannel := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
errChannel <- conn.Handshake()
|
||||||
|
close(errChannel)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case err := <-errChannel:
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
conn.Close()
|
||||||
|
return nil, nil, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The negotiated protocol can be either of the following:
|
||||||
|
// 1. h2: When the server supports ALPN. Only HTTP/2 can be negotiated since
|
||||||
|
// it is the only protocol advertised by the client during the handshake.
|
||||||
|
// The tls library ensures that the server chooses a protocol advertised
|
||||||
|
// by the client.
|
||||||
|
// 2. "" (empty string): If the server doesn't support ALPN. ALPN is a requirement
|
||||||
|
// for using HTTP/2 over TLS. We can terminate the connection immediately.
|
||||||
|
np := conn.ConnectionState().NegotiatedProtocol
|
||||||
|
if np == "" {
|
||||||
|
if envconfig.EnforceALPNEnabled {
|
||||||
|
conn.Close()
|
||||||
|
return nil, nil, fmt.Errorf("credentials: cannot check peer: missing selected ALPN property. %s", alpnFailureHelpMessage)
|
||||||
|
}
|
||||||
|
logger.Warningf("Allowing TLS connection to server %q with ALPN disabled. TLS connections to servers with ALPN disabled will be disallowed in future grpc-go releases", cfg.ServerName)
|
||||||
|
}
|
||||||
|
tlsInfo := TLSInfo{
|
||||||
|
State: conn.ConnectionState(),
|
||||||
|
CommonAuthInfo: CommonAuthInfo{
|
||||||
|
SecurityLevel: PrivacyAndIntegrity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
id := credinternal.SPIFFEIDFromState(conn.ConnectionState())
|
||||||
|
if id != nil {
|
||||||
|
tlsInfo.SPIFFEID = id
|
||||||
|
}
|
||||||
|
return credinternal.WrapSyscallConn(rawConn, conn), tlsInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) {
|
||||||
|
conn := tls.Server(rawConn, c.config)
|
||||||
|
if err := conn.Handshake(); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
cs := conn.ConnectionState()
|
||||||
|
// The negotiated application protocol can be empty only if the client doesn't
|
||||||
|
// support ALPN. In such cases, we can close the connection since ALPN is required
|
||||||
|
// for using HTTP/2 over TLS.
|
||||||
|
if cs.NegotiatedProtocol == "" {
|
||||||
|
if envconfig.EnforceALPNEnabled {
|
||||||
|
conn.Close()
|
||||||
|
return nil, nil, fmt.Errorf("credentials: cannot check peer: missing selected ALPN property. %s", alpnFailureHelpMessage)
|
||||||
|
} else if logger.V(2) {
|
||||||
|
logger.Info("Allowing TLS connection from client with ALPN disabled. TLS connections with ALPN disabled will be disallowed in future grpc-go releases")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tlsInfo := TLSInfo{
|
||||||
|
State: cs,
|
||||||
|
CommonAuthInfo: CommonAuthInfo{
|
||||||
|
SecurityLevel: PrivacyAndIntegrity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
id := credinternal.SPIFFEIDFromState(conn.ConnectionState())
|
||||||
|
if id != nil {
|
||||||
|
tlsInfo.SPIFFEID = id
|
||||||
|
}
|
||||||
|
return credinternal.WrapSyscallConn(rawConn, conn), tlsInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsCreds) Clone() TransportCredentials {
|
||||||
|
return NewTLS(c.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsCreds) OverrideServerName(serverNameOverride string) error {
|
||||||
|
c.config.ServerName = serverNameOverride
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following cipher suites are forbidden for use with HTTP/2 by
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7540#appendix-A
|
||||||
|
var tls12ForbiddenCipherSuites = map[uint16]struct{}{
|
||||||
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA: {},
|
||||||
|
tls.TLS_RSA_WITH_AES_256_CBC_SHA: {},
|
||||||
|
tls.TLS_RSA_WITH_AES_128_GCM_SHA256: {},
|
||||||
|
tls.TLS_RSA_WITH_AES_256_GCM_SHA384: {},
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: {},
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: {},
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: {},
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTLS uses c to construct a TransportCredentials based on TLS.
|
||||||
|
func NewTLS(c *tls.Config) TransportCredentials {
|
||||||
|
config := applyDefaults(c)
|
||||||
|
if config.GetConfigForClient != nil {
|
||||||
|
oldFn := config.GetConfigForClient
|
||||||
|
config.GetConfigForClient = func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||||
|
cfgForClient, err := oldFn(hello)
|
||||||
|
if err != nil || cfgForClient == nil {
|
||||||
|
return cfgForClient, err
|
||||||
|
}
|
||||||
|
return applyDefaults(cfgForClient), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &tlsCreds{config: config}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyDefaults(c *tls.Config) *tls.Config {
|
||||||
|
config := credinternal.CloneTLSConfig(c)
|
||||||
|
config.NextProtos = credinternal.AppendH2ToNextProtos(config.NextProtos)
|
||||||
|
// If the user did not configure a MinVersion and did not configure a
|
||||||
|
// MaxVersion < 1.2, use MinVersion=1.2, which is required by
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7540#section-9.2
|
||||||
|
if config.MinVersion == 0 && (config.MaxVersion == 0 || config.MaxVersion >= tls.VersionTLS12) {
|
||||||
|
config.MinVersion = tls.VersionTLS12
|
||||||
|
}
|
||||||
|
// If the user did not configure CipherSuites, use all "secure" cipher
|
||||||
|
// suites reported by the TLS package, but remove some explicitly forbidden
|
||||||
|
// by https://datatracker.ietf.org/doc/html/rfc7540#appendix-A
|
||||||
|
if config.CipherSuites == nil {
|
||||||
|
for _, cs := range tls.CipherSuites() {
|
||||||
|
if _, ok := tls12ForbiddenCipherSuites[cs.ID]; !ok {
|
||||||
|
config.CipherSuites = append(config.CipherSuites, cs.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientTLSFromCert constructs TLS credentials from the provided root
|
||||||
|
// certificate authority certificate(s) to validate server connections. If
|
||||||
|
// certificates to establish the identity of the client need to be included in
|
||||||
|
// the credentials (eg: for mTLS), use NewTLS instead, where a complete
|
||||||
|
// tls.Config can be specified.
|
||||||
|
//
|
||||||
|
// serverNameOverride is for testing only. If set to a non empty string, it will
|
||||||
|
// override the virtual host name of authority (e.g. :authority header field) in
|
||||||
|
// requests. Users should use grpc.WithAuthority passed to grpc.NewClient to
|
||||||
|
// override the authority of the client instead.
|
||||||
|
func NewClientTLSFromCert(cp *x509.CertPool, serverNameOverride string) TransportCredentials {
|
||||||
|
return NewTLS(&tls.Config{ServerName: serverNameOverride, RootCAs: cp})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientTLSFromFile constructs TLS credentials from the provided root
|
||||||
|
// certificate authority certificate file(s) to validate server connections. If
|
||||||
|
// certificates to establish the identity of the client need to be included in
|
||||||
|
// the credentials (eg: for mTLS), use NewTLS instead, where a complete
|
||||||
|
// tls.Config can be specified.
|
||||||
|
//
|
||||||
|
// serverNameOverride is for testing only. If set to a non empty string, it will
|
||||||
|
// override the virtual host name of authority (e.g. :authority header field) in
|
||||||
|
// requests. Users should use grpc.WithAuthority passed to grpc.NewClient to
|
||||||
|
// override the authority of the client instead.
|
||||||
|
func NewClientTLSFromFile(certFile, serverNameOverride string) (TransportCredentials, error) {
|
||||||
|
b, err := os.ReadFile(certFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cp := x509.NewCertPool()
|
||||||
|
if !cp.AppendCertsFromPEM(b) {
|
||||||
|
return nil, fmt.Errorf("credentials: failed to append certificates")
|
||||||
|
}
|
||||||
|
return NewTLS(&tls.Config{ServerName: serverNameOverride, RootCAs: cp}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServerTLSFromCert constructs TLS credentials from the input certificate for server.
|
||||||
|
func NewServerTLSFromCert(cert *tls.Certificate) TransportCredentials {
|
||||||
|
return NewTLS(&tls.Config{Certificates: []tls.Certificate{*cert}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServerTLSFromFile constructs TLS credentials from the input certificate file and key
|
||||||
|
// file for server.
|
||||||
|
func NewServerTLSFromFile(certFile, keyFile string) (TransportCredentials, error) {
|
||||||
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSChannelzSecurityValue defines the struct that TLS protocol should return
|
||||||
|
// from GetSecurityValue(), containing security info like cipher and certificate used.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
type TLSChannelzSecurityValue struct {
|
||||||
|
ChannelzSecurityValue
|
||||||
|
StandardName string
|
||||||
|
LocalCertificate []byte
|
||||||
|
RemoteCertificate []byte
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,797 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2018 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 grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/backoff"
|
||||||
|
"google.golang.org/grpc/channelz"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/internal"
|
||||||
|
internalbackoff "google.golang.org/grpc/internal/backoff"
|
||||||
|
"google.golang.org/grpc/internal/binarylog"
|
||||||
|
"google.golang.org/grpc/internal/transport"
|
||||||
|
"google.golang.org/grpc/keepalive"
|
||||||
|
"google.golang.org/grpc/mem"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
"google.golang.org/grpc/stats"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// https://github.com/grpc/proposal/blob/master/A6-client-retries.md#limits-on-retries-and-hedges
|
||||||
|
defaultMaxCallAttempts = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
internal.AddGlobalDialOptions = func(opt ...DialOption) {
|
||||||
|
globalDialOptions = append(globalDialOptions, opt...)
|
||||||
|
}
|
||||||
|
internal.ClearGlobalDialOptions = func() {
|
||||||
|
globalDialOptions = nil
|
||||||
|
}
|
||||||
|
internal.AddGlobalPerTargetDialOptions = func(opt any) {
|
||||||
|
if ptdo, ok := opt.(perTargetDialOption); ok {
|
||||||
|
globalPerTargetDialOptions = append(globalPerTargetDialOptions, ptdo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal.ClearGlobalPerTargetDialOptions = func() {
|
||||||
|
globalPerTargetDialOptions = nil
|
||||||
|
}
|
||||||
|
internal.WithBinaryLogger = withBinaryLogger
|
||||||
|
internal.JoinDialOptions = newJoinDialOption
|
||||||
|
internal.DisableGlobalDialOptions = newDisableGlobalDialOptions
|
||||||
|
internal.WithBufferPool = withBufferPool
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialOptions configure a Dial call. dialOptions are set by the DialOption
|
||||||
|
// values passed to Dial.
|
||||||
|
type dialOptions struct {
|
||||||
|
unaryInt UnaryClientInterceptor
|
||||||
|
streamInt StreamClientInterceptor
|
||||||
|
|
||||||
|
chainUnaryInts []UnaryClientInterceptor
|
||||||
|
chainStreamInts []StreamClientInterceptor
|
||||||
|
|
||||||
|
compressorV0 Compressor
|
||||||
|
dc Decompressor
|
||||||
|
bs internalbackoff.Strategy
|
||||||
|
block bool
|
||||||
|
returnLastError bool
|
||||||
|
timeout time.Duration
|
||||||
|
authority string
|
||||||
|
binaryLogger binarylog.Logger
|
||||||
|
copts transport.ConnectOptions
|
||||||
|
callOptions []CallOption
|
||||||
|
channelzParent channelz.Identifier
|
||||||
|
disableServiceConfig bool
|
||||||
|
disableRetry bool
|
||||||
|
disableHealthCheck bool
|
||||||
|
minConnectTimeout func() time.Duration
|
||||||
|
defaultServiceConfig *ServiceConfig // defaultServiceConfig is parsed from defaultServiceConfigRawJSON.
|
||||||
|
defaultServiceConfigRawJSON *string
|
||||||
|
resolvers []resolver.Builder
|
||||||
|
idleTimeout time.Duration
|
||||||
|
defaultScheme string
|
||||||
|
maxCallAttempts int
|
||||||
|
enableLocalDNSResolution bool // Specifies if target hostnames should be resolved when proxying is enabled.
|
||||||
|
useProxy bool // Specifies if a server should be connected via proxy.
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialOption configures how we set up the connection.
|
||||||
|
type DialOption interface {
|
||||||
|
apply(*dialOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalDialOptions []DialOption
|
||||||
|
|
||||||
|
// perTargetDialOption takes a parsed target and returns a dial option to apply.
|
||||||
|
//
|
||||||
|
// This gets called after NewClient() parses the target, and allows per target
|
||||||
|
// configuration set through a returned DialOption. The DialOption will not take
|
||||||
|
// effect if specifies a resolver builder, as that Dial Option is factored in
|
||||||
|
// while parsing target.
|
||||||
|
type perTargetDialOption interface {
|
||||||
|
// DialOption returns a Dial Option to apply.
|
||||||
|
DialOptionForTarget(parsedTarget url.URL) DialOption
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalPerTargetDialOptions []perTargetDialOption
|
||||||
|
|
||||||
|
// EmptyDialOption does not alter the dial configuration. It can be embedded in
|
||||||
|
// another structure to build custom dial options.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
type EmptyDialOption struct{}
|
||||||
|
|
||||||
|
func (EmptyDialOption) apply(*dialOptions) {}
|
||||||
|
|
||||||
|
type disableGlobalDialOptions struct{}
|
||||||
|
|
||||||
|
func (disableGlobalDialOptions) apply(*dialOptions) {}
|
||||||
|
|
||||||
|
// newDisableGlobalDialOptions returns a DialOption that prevents the ClientConn
|
||||||
|
// from applying the global DialOptions (set via AddGlobalDialOptions).
|
||||||
|
func newDisableGlobalDialOptions() DialOption {
|
||||||
|
return &disableGlobalDialOptions{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// funcDialOption wraps a function that modifies dialOptions into an
|
||||||
|
// implementation of the DialOption interface.
|
||||||
|
type funcDialOption struct {
|
||||||
|
f func(*dialOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fdo *funcDialOption) apply(do *dialOptions) {
|
||||||
|
fdo.f(do)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncDialOption(f func(*dialOptions)) *funcDialOption {
|
||||||
|
return &funcDialOption{
|
||||||
|
f: f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type joinDialOption struct {
|
||||||
|
opts []DialOption
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jdo *joinDialOption) apply(do *dialOptions) {
|
||||||
|
for _, opt := range jdo.opts {
|
||||||
|
opt.apply(do)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJoinDialOption(opts ...DialOption) DialOption {
|
||||||
|
return &joinDialOption{opts: opts}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSharedWriteBuffer allows reusing per-connection transport write buffer.
|
||||||
|
// If this option is set to true every connection will release the buffer after
|
||||||
|
// flushing the data on the wire.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func WithSharedWriteBuffer(val bool) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.SharedWriteBuffer = val
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithWriteBufferSize determines how much data can be batched before doing a
|
||||||
|
// write on the wire. The default value for this buffer is 32KB.
|
||||||
|
//
|
||||||
|
// Zero or negative values will disable the write buffer such that each write
|
||||||
|
// will be on underlying connection. Note: A Send call may not directly
|
||||||
|
// translate to a write.
|
||||||
|
func WithWriteBufferSize(s int) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.WriteBufferSize = s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithReadBufferSize lets you set the size of read buffer, this determines how
|
||||||
|
// much data can be read at most for each read syscall.
|
||||||
|
//
|
||||||
|
// The default value for this buffer is 32KB. Zero or negative values will
|
||||||
|
// disable read buffer for a connection so data framer can access the
|
||||||
|
// underlying conn directly.
|
||||||
|
func WithReadBufferSize(s int) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.ReadBufferSize = s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInitialWindowSize returns a DialOption which sets the value for initial
|
||||||
|
// window size on a stream. The lower bound for window size is 64K and any value
|
||||||
|
// smaller than that will be ignored.
|
||||||
|
func WithInitialWindowSize(s int32) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.InitialWindowSize = s
|
||||||
|
o.copts.StaticWindowSize = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInitialConnWindowSize returns a DialOption which sets the value for
|
||||||
|
// initial window size on a connection. The lower bound for window size is 64K
|
||||||
|
// and any value smaller than that will be ignored.
|
||||||
|
func WithInitialConnWindowSize(s int32) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.InitialConnWindowSize = s
|
||||||
|
o.copts.StaticWindowSize = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStaticStreamWindowSize returns a DialOption which sets the initial
|
||||||
|
// stream window size to the value provided and disables dynamic flow control.
|
||||||
|
func WithStaticStreamWindowSize(s int32) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.InitialWindowSize = s
|
||||||
|
o.copts.StaticWindowSize = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStaticConnWindowSize returns a DialOption which sets the initial
|
||||||
|
// connection window size to the value provided and disables dynamic flow
|
||||||
|
// control.
|
||||||
|
func WithStaticConnWindowSize(s int32) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.InitialConnWindowSize = s
|
||||||
|
o.copts.StaticWindowSize = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxMsgSize returns a DialOption which sets the maximum message size the
|
||||||
|
// client can receive.
|
||||||
|
//
|
||||||
|
// Deprecated: use WithDefaultCallOptions(MaxCallRecvMsgSize(s)) instead. Will
|
||||||
|
// be supported throughout 1.x.
|
||||||
|
func WithMaxMsgSize(s int) DialOption {
|
||||||
|
return WithDefaultCallOptions(MaxCallRecvMsgSize(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaultCallOptions returns a DialOption which sets the default
|
||||||
|
// CallOptions for calls over the connection.
|
||||||
|
func WithDefaultCallOptions(cos ...CallOption) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.callOptions = append(o.callOptions, cos...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCodec returns a DialOption which sets a codec for message marshaling and
|
||||||
|
// unmarshaling.
|
||||||
|
//
|
||||||
|
// Deprecated: use WithDefaultCallOptions(ForceCodec(_)) instead. Will be
|
||||||
|
// supported throughout 1.x.
|
||||||
|
func WithCodec(c Codec) DialOption {
|
||||||
|
return WithDefaultCallOptions(CallCustomCodec(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCompressor returns a DialOption which sets a Compressor to use for
|
||||||
|
// message compression. It has lower priority than the compressor set by the
|
||||||
|
// UseCompressor CallOption.
|
||||||
|
//
|
||||||
|
// Deprecated: use UseCompressor instead. Will be supported throughout 1.x.
|
||||||
|
func WithCompressor(cp Compressor) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.compressorV0 = cp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDecompressor returns a DialOption which sets a Decompressor to use for
|
||||||
|
// incoming message decompression. If incoming response messages are encoded
|
||||||
|
// using the decompressor's Type(), it will be used. Otherwise, the message
|
||||||
|
// encoding will be used to look up the compressor registered via
|
||||||
|
// encoding.RegisterCompressor, which will then be used to decompress the
|
||||||
|
// message. If no compressor is registered for the encoding, an Unimplemented
|
||||||
|
// status error will be returned.
|
||||||
|
//
|
||||||
|
// Deprecated: use encoding.RegisterCompressor instead. Will be supported
|
||||||
|
// throughout 1.x.
|
||||||
|
func WithDecompressor(dc Decompressor) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.dc = dc
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConnectParams configures the ClientConn to use the provided ConnectParams
|
||||||
|
// for creating and maintaining connections to servers.
|
||||||
|
//
|
||||||
|
// The backoff configuration specified as part of the ConnectParams overrides
|
||||||
|
// all defaults specified in
|
||||||
|
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md. Consider
|
||||||
|
// using the backoff.DefaultConfig as a base, in cases where you want to
|
||||||
|
// override only a subset of the backoff configuration.
|
||||||
|
func WithConnectParams(p ConnectParams) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.bs = internalbackoff.Exponential{Config: p.Backoff}
|
||||||
|
o.minConnectTimeout = func() time.Duration {
|
||||||
|
return p.MinConnectTimeout
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBackoffMaxDelay configures the dialer to use the provided maximum delay
|
||||||
|
// when backing off after failed connection attempts.
|
||||||
|
//
|
||||||
|
// Deprecated: use WithConnectParams instead. Will be supported throughout 1.x.
|
||||||
|
func WithBackoffMaxDelay(md time.Duration) DialOption {
|
||||||
|
return WithBackoffConfig(BackoffConfig{MaxDelay: md})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBackoffConfig configures the dialer to use the provided backoff
|
||||||
|
// parameters after connection failures.
|
||||||
|
//
|
||||||
|
// Deprecated: use WithConnectParams instead. Will be supported throughout 1.x.
|
||||||
|
func WithBackoffConfig(b BackoffConfig) DialOption {
|
||||||
|
bc := backoff.DefaultConfig
|
||||||
|
bc.MaxDelay = b.MaxDelay
|
||||||
|
return withBackoff(internalbackoff.Exponential{Config: bc})
|
||||||
|
}
|
||||||
|
|
||||||
|
// withBackoff sets the backoff strategy used for connectRetryNum after a failed
|
||||||
|
// connection attempt.
|
||||||
|
//
|
||||||
|
// This can be exported if arbitrary backoff strategies are allowed by gRPC.
|
||||||
|
func withBackoff(bs internalbackoff.Strategy) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.bs = bs
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBlock returns a DialOption which makes callers of Dial block until the
|
||||||
|
// underlying connection is up. Without this, Dial returns immediately and
|
||||||
|
// connecting the server happens in background.
|
||||||
|
//
|
||||||
|
// Use of this feature is not recommended. For more information, please see:
|
||||||
|
// https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md
|
||||||
|
//
|
||||||
|
// Deprecated: this DialOption is not supported by NewClient.
|
||||||
|
// Will be supported throughout 1.x.
|
||||||
|
func WithBlock() DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.block = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithReturnConnectionError returns a DialOption which makes the client connection
|
||||||
|
// return a string containing both the last connection error that occurred and
|
||||||
|
// the context.DeadlineExceeded error.
|
||||||
|
// Implies WithBlock()
|
||||||
|
//
|
||||||
|
// Use of this feature is not recommended. For more information, please see:
|
||||||
|
// https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md
|
||||||
|
//
|
||||||
|
// Deprecated: this DialOption is not supported by NewClient.
|
||||||
|
// Will be supported throughout 1.x.
|
||||||
|
func WithReturnConnectionError() DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.block = true
|
||||||
|
o.returnLastError = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInsecure returns a DialOption which disables transport security for this
|
||||||
|
// ClientConn. Under the hood, it uses insecure.NewCredentials().
|
||||||
|
//
|
||||||
|
// Note that using this DialOption with per-RPC credentials (through
|
||||||
|
// WithCredentialsBundle or WithPerRPCCredentials) which require transport
|
||||||
|
// security is incompatible and will cause RPCs to fail.
|
||||||
|
//
|
||||||
|
// Deprecated: use WithTransportCredentials and insecure.NewCredentials()
|
||||||
|
// instead. Will be supported throughout 1.x.
|
||||||
|
func WithInsecure() DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.TransportCredentials = insecure.NewCredentials()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNoProxy returns a DialOption which disables the use of proxies for this
|
||||||
|
// ClientConn. This is ignored if WithDialer or WithContextDialer are used.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func WithNoProxy() DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.useProxy = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLocalDNSResolution forces local DNS name resolution even when a proxy is
|
||||||
|
// specified in the environment. By default, the server name is provided
|
||||||
|
// directly to the proxy as part of the CONNECT handshake. This is ignored if
|
||||||
|
// WithNoProxy is used.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func WithLocalDNSResolution() DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.enableLocalDNSResolution = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTransportCredentials returns a DialOption which configures a connection
|
||||||
|
// level security credentials (e.g., TLS/SSL). This should not be used together
|
||||||
|
// with WithCredentialsBundle.
|
||||||
|
func WithTransportCredentials(creds credentials.TransportCredentials) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.TransportCredentials = creds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPerRPCCredentials returns a DialOption which sets credentials and places
|
||||||
|
// auth state on each outbound RPC.
|
||||||
|
func WithPerRPCCredentials(creds credentials.PerRPCCredentials) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.PerRPCCredentials = append(o.copts.PerRPCCredentials, creds)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCredentialsBundle returns a DialOption to set a credentials bundle for
|
||||||
|
// the ClientConn.WithCreds. This should not be used together with
|
||||||
|
// WithTransportCredentials.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func WithCredentialsBundle(b credentials.Bundle) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.CredsBundle = b
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout returns a DialOption that configures a timeout for dialing a
|
||||||
|
// ClientConn initially. This is valid if and only if WithBlock() is present.
|
||||||
|
//
|
||||||
|
// Deprecated: this DialOption is not supported by NewClient.
|
||||||
|
// Will be supported throughout 1.x.
|
||||||
|
func WithTimeout(d time.Duration) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.timeout = d
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContextDialer returns a DialOption that sets a dialer to create
|
||||||
|
// connections. If FailOnNonTempDialError() is set to true, and an error is
|
||||||
|
// returned by f, gRPC checks the error's Temporary() method to decide if it
|
||||||
|
// should try to reconnect to the network address.
|
||||||
|
//
|
||||||
|
// Note that gRPC by default performs name resolution on the target passed to
|
||||||
|
// NewClient. To bypass name resolution and cause the target string to be
|
||||||
|
// passed directly to the dialer here instead, use the "passthrough" resolver
|
||||||
|
// by specifying it in the target string, e.g. "passthrough:target".
|
||||||
|
//
|
||||||
|
// Note: All supported releases of Go (as of December 2023) override the OS
|
||||||
|
// defaults for TCP keepalive time and interval to 15s. To enable TCP keepalive
|
||||||
|
// with OS defaults for keepalive time and interval, use a net.Dialer that sets
|
||||||
|
// the KeepAlive field to a negative value, and sets the SO_KEEPALIVE socket
|
||||||
|
// option to true from the Control field. For a concrete example of how to do
|
||||||
|
// this, see internal.NetDialerWithTCPKeepalive().
|
||||||
|
//
|
||||||
|
// For more information, please see [issue 23459] in the Go GitHub repo.
|
||||||
|
//
|
||||||
|
// [issue 23459]: https://github.com/golang/go/issues/23459
|
||||||
|
func WithContextDialer(f func(context.Context, string) (net.Conn, error)) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.Dialer = f
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDialer returns a DialOption that specifies a function to use for dialing
|
||||||
|
// network addresses. If FailOnNonTempDialError() is set to true, and an error
|
||||||
|
// is returned by f, gRPC checks the error's Temporary() method to decide if it
|
||||||
|
// should try to reconnect to the network address.
|
||||||
|
//
|
||||||
|
// Deprecated: use WithContextDialer instead. Will be supported throughout
|
||||||
|
// 1.x.
|
||||||
|
func WithDialer(f func(string, time.Duration) (net.Conn, error)) DialOption {
|
||||||
|
return WithContextDialer(
|
||||||
|
func(ctx context.Context, addr string) (net.Conn, error) {
|
||||||
|
if deadline, ok := ctx.Deadline(); ok {
|
||||||
|
return f(addr, time.Until(deadline))
|
||||||
|
}
|
||||||
|
return f(addr, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStatsHandler returns a DialOption that specifies the stats handler for
|
||||||
|
// all the RPCs and underlying network connections in this ClientConn.
|
||||||
|
func WithStatsHandler(h stats.Handler) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
if h == nil {
|
||||||
|
logger.Error("ignoring nil parameter in grpc.WithStatsHandler ClientOption")
|
||||||
|
// Do not allow a nil stats handler, which would otherwise cause
|
||||||
|
// panics.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.copts.StatsHandlers = append(o.copts.StatsHandlers, h)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// withBinaryLogger returns a DialOption that specifies the binary logger for
|
||||||
|
// this ClientConn.
|
||||||
|
func withBinaryLogger(bl binarylog.Logger) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.binaryLogger = bl
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FailOnNonTempDialError returns a DialOption that specifies if gRPC fails on
|
||||||
|
// non-temporary dial errors. If f is true, and dialer returns a non-temporary
|
||||||
|
// error, gRPC will fail the connection to the network address and won't try to
|
||||||
|
// reconnect. The default value of FailOnNonTempDialError is false.
|
||||||
|
//
|
||||||
|
// FailOnNonTempDialError only affects the initial dial, and does not do
|
||||||
|
// anything useful unless you are also using WithBlock().
|
||||||
|
//
|
||||||
|
// Use of this feature is not recommended. For more information, please see:
|
||||||
|
// https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md
|
||||||
|
//
|
||||||
|
// Deprecated: this DialOption is not supported by NewClient.
|
||||||
|
// This API may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func FailOnNonTempDialError(f bool) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.FailOnNonTempDialError = f
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUserAgent returns a DialOption that specifies a user agent string for all
|
||||||
|
// the RPCs.
|
||||||
|
func WithUserAgent(s string) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.UserAgent = s + " " + grpcUA
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithKeepaliveParams returns a DialOption that specifies keepalive parameters
|
||||||
|
// for the client transport.
|
||||||
|
//
|
||||||
|
// Keepalive is disabled by default.
|
||||||
|
func WithKeepaliveParams(kp keepalive.ClientParameters) DialOption {
|
||||||
|
if kp.Time < internal.KeepaliveMinPingTime {
|
||||||
|
logger.Warningf("Adjusting keepalive ping interval to minimum period of %v", internal.KeepaliveMinPingTime)
|
||||||
|
kp.Time = internal.KeepaliveMinPingTime
|
||||||
|
}
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.KeepaliveParams = kp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUnaryInterceptor returns a DialOption that specifies the interceptor for
|
||||||
|
// unary RPCs.
|
||||||
|
func WithUnaryInterceptor(f UnaryClientInterceptor) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.unaryInt = f
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithChainUnaryInterceptor returns a DialOption that specifies the chained
|
||||||
|
// interceptor for unary RPCs. The first interceptor will be the outer most,
|
||||||
|
// while the last interceptor will be the inner most wrapper around the real call.
|
||||||
|
// All interceptors added by this method will be chained, and the interceptor
|
||||||
|
// defined by WithUnaryInterceptor will always be prepended to the chain.
|
||||||
|
func WithChainUnaryInterceptor(interceptors ...UnaryClientInterceptor) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.chainUnaryInts = append(o.chainUnaryInts, interceptors...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStreamInterceptor returns a DialOption that specifies the interceptor for
|
||||||
|
// streaming RPCs.
|
||||||
|
func WithStreamInterceptor(f StreamClientInterceptor) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.streamInt = f
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithChainStreamInterceptor returns a DialOption that specifies the chained
|
||||||
|
// interceptor for streaming RPCs. The first interceptor will be the outer most,
|
||||||
|
// while the last interceptor will be the inner most wrapper around the real call.
|
||||||
|
// All interceptors added by this method will be chained, and the interceptor
|
||||||
|
// defined by WithStreamInterceptor will always be prepended to the chain.
|
||||||
|
func WithChainStreamInterceptor(interceptors ...StreamClientInterceptor) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.chainStreamInts = append(o.chainStreamInts, interceptors...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAuthority returns a DialOption that specifies the value to be used as the
|
||||||
|
// :authority pseudo-header and as the server name in authentication handshake.
|
||||||
|
// This overrides all other ways of setting authority on the channel, but can be
|
||||||
|
// overridden per-call by using grpc.CallAuthority.
|
||||||
|
func WithAuthority(a string) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.authority = a
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithChannelzParentID returns a DialOption that specifies the channelz ID of
|
||||||
|
// current ClientConn's parent. This function is used in nested channel creation
|
||||||
|
// (e.g. grpclb dial).
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func WithChannelzParentID(c channelz.Identifier) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.channelzParent = c
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDisableServiceConfig returns a DialOption that causes gRPC to ignore any
|
||||||
|
// service config provided by the resolver and provides a hint to the resolver
|
||||||
|
// to not fetch service configs.
|
||||||
|
//
|
||||||
|
// Note that this dial option only disables service config from resolver. If
|
||||||
|
// default service config is provided, gRPC will use the default service config.
|
||||||
|
func WithDisableServiceConfig() DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.disableServiceConfig = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaultServiceConfig returns a DialOption that configures the default
|
||||||
|
// service config, which will be used in cases where:
|
||||||
|
//
|
||||||
|
// 1. WithDisableServiceConfig is also used, or
|
||||||
|
//
|
||||||
|
// 2. The name resolver does not provide a service config or provides an
|
||||||
|
// invalid service config.
|
||||||
|
//
|
||||||
|
// The parameter s is the JSON representation of the default service config.
|
||||||
|
// For more information about service configs, see:
|
||||||
|
// https://github.com/grpc/grpc/blob/master/doc/service_config.md
|
||||||
|
// For a simple example of usage, see:
|
||||||
|
// examples/features/load_balancing/client/main.go
|
||||||
|
func WithDefaultServiceConfig(s string) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.defaultServiceConfigRawJSON = &s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDisableRetry returns a DialOption that disables retries, even if the
|
||||||
|
// service config enables them. This does not impact transparent retries, which
|
||||||
|
// will happen automatically if no data is written to the wire or if the RPC is
|
||||||
|
// unprocessed by the remote server.
|
||||||
|
func WithDisableRetry() DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.disableRetry = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxHeaderListSizeDialOption is a DialOption that specifies the maximum
|
||||||
|
// (uncompressed) size of header list that the client is prepared to accept.
|
||||||
|
type MaxHeaderListSizeDialOption struct {
|
||||||
|
MaxHeaderListSize uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o MaxHeaderListSizeDialOption) apply(do *dialOptions) {
|
||||||
|
do.copts.MaxHeaderListSize = &o.MaxHeaderListSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxHeaderListSize returns a DialOption that specifies the maximum
|
||||||
|
// (uncompressed) size of header list that the client is prepared to accept.
|
||||||
|
func WithMaxHeaderListSize(s uint32) DialOption {
|
||||||
|
return MaxHeaderListSizeDialOption{
|
||||||
|
MaxHeaderListSize: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDisableHealthCheck disables the LB channel health checking for all
|
||||||
|
// SubConns of this ClientConn.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func WithDisableHealthCheck() DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.disableHealthCheck = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultDialOptions() dialOptions {
|
||||||
|
return dialOptions{
|
||||||
|
copts: transport.ConnectOptions{
|
||||||
|
ReadBufferSize: defaultReadBufSize,
|
||||||
|
WriteBufferSize: defaultWriteBufSize,
|
||||||
|
UserAgent: grpcUA,
|
||||||
|
BufferPool: mem.DefaultBufferPool(),
|
||||||
|
},
|
||||||
|
bs: internalbackoff.DefaultExponential,
|
||||||
|
idleTimeout: 30 * time.Minute,
|
||||||
|
defaultScheme: "dns",
|
||||||
|
maxCallAttempts: defaultMaxCallAttempts,
|
||||||
|
useProxy: true,
|
||||||
|
enableLocalDNSResolution: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// withMinConnectDeadline specifies the function that clientconn uses to
|
||||||
|
// get minConnectDeadline. This can be used to make connection attempts happen
|
||||||
|
// faster/slower.
|
||||||
|
//
|
||||||
|
// For testing purpose only.
|
||||||
|
func withMinConnectDeadline(f func() time.Duration) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.minConnectTimeout = f
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// withDefaultScheme is used to allow Dial to use "passthrough" as the default
|
||||||
|
// name resolver, while NewClient uses "dns" otherwise.
|
||||||
|
func withDefaultScheme(s string) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.defaultScheme = s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithResolvers allows a list of resolver implementations to be registered
|
||||||
|
// locally with the ClientConn without needing to be globally registered via
|
||||||
|
// resolver.Register. They will be matched against the scheme used for the
|
||||||
|
// current Dial only, and will take precedence over the global registry.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func WithResolvers(rs ...resolver.Builder) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.resolvers = append(o.resolvers, rs...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithIdleTimeout returns a DialOption that configures an idle timeout for the
|
||||||
|
// channel. If the channel is idle for the configured timeout, i.e there are no
|
||||||
|
// ongoing RPCs and no new RPCs are initiated, the channel will enter idle mode
|
||||||
|
// and as a result the name resolver and load balancer will be shut down. The
|
||||||
|
// channel will exit idle mode when the Connect() method is called or when an
|
||||||
|
// RPC is initiated.
|
||||||
|
//
|
||||||
|
// A default timeout of 30 minutes will be used if this dial option is not set
|
||||||
|
// at dial time and idleness can be disabled by passing a timeout of zero.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func WithIdleTimeout(d time.Duration) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.idleTimeout = d
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxCallAttempts returns a DialOption that configures the maximum number
|
||||||
|
// of attempts per call (including retries and hedging) using the channel.
|
||||||
|
// Service owners may specify a higher value for these parameters, but higher
|
||||||
|
// values will be treated as equal to the maximum value by the client
|
||||||
|
// implementation. This mitigates security concerns related to the service
|
||||||
|
// config being transferred to the client via DNS.
|
||||||
|
//
|
||||||
|
// A value of 5 will be used if this dial option is not set or n < 2.
|
||||||
|
func WithMaxCallAttempts(n int) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
if n < 2 {
|
||||||
|
n = defaultMaxCallAttempts
|
||||||
|
}
|
||||||
|
o.maxCallAttempts = n
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func withBufferPool(bufferPool mem.BufferPool) DialOption {
|
||||||
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
|
o.copts.BufferPool = bufferPool
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2015 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 ./scripts/regenerate.sh
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package grpc implements an RPC system called gRPC.
|
||||||
|
|
||||||
|
See grpc.io for more information about gRPC.
|
||||||
|
*/
|
||||||
|
package grpc // import "google.golang.org/grpc"
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 encoding defines the interface for the compressor and codec, and
|
||||||
|
// functions to register and retrieve compressors and codecs.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This package is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
package encoding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/encoding/internal"
|
||||||
|
"google.golang.org/grpc/internal/grpcutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Identity specifies the optional encoding for uncompressed streams.
|
||||||
|
// It is intended for grpc internal use only.
|
||||||
|
const Identity = "identity"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
internal.RegisterCompressorForTesting = func(c Compressor) func() {
|
||||||
|
name := c.Name()
|
||||||
|
curCompressor, found := registeredCompressor[name]
|
||||||
|
RegisterCompressor(c)
|
||||||
|
return func() {
|
||||||
|
if found {
|
||||||
|
registeredCompressor[name] = curCompressor
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(registeredCompressor, name)
|
||||||
|
grpcutil.RegisteredCompressorNames = slices.DeleteFunc(grpcutil.RegisteredCompressorNames, func(s string) bool {
|
||||||
|
return s == name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compressor is used for compressing and decompressing when sending or
|
||||||
|
// receiving messages.
|
||||||
|
type Compressor interface {
|
||||||
|
// Compress writes the data written to wc to w after compressing it. If an
|
||||||
|
// error occurs while initializing the compressor, that error is returned
|
||||||
|
// instead.
|
||||||
|
Compress(w io.Writer) (io.WriteCloser, error)
|
||||||
|
// Decompress reads data from r, decompresses it, and provides the
|
||||||
|
// uncompressed data via the returned io.Reader. If an error occurs while
|
||||||
|
// initializing the decompressor, that error is returned instead.
|
||||||
|
Decompress(r io.Reader) (io.Reader, error)
|
||||||
|
// Name is the name of the compression codec and is used to set the content
|
||||||
|
// coding header. The result must be static; the result cannot change
|
||||||
|
// between calls.
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
var registeredCompressor = make(map[string]Compressor)
|
||||||
|
|
||||||
|
// RegisterCompressor registers the compressor with gRPC by its name. It can
|
||||||
|
// be activated when sending an RPC via grpc.UseCompressor(). It will be
|
||||||
|
// automatically accessed when receiving a message based on the content coding
|
||||||
|
// header. Servers also use it to send a response with the same encoding as
|
||||||
|
// the request.
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple Compressors are
|
||||||
|
// registered with the same name, the one registered last will take effect.
|
||||||
|
func RegisterCompressor(c Compressor) {
|
||||||
|
registeredCompressor[c.Name()] = c
|
||||||
|
if !grpcutil.IsCompressorNameRegistered(c.Name()) {
|
||||||
|
grpcutil.RegisteredCompressorNames = append(grpcutil.RegisteredCompressorNames, c.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCompressor returns Compressor for the given compressor name.
|
||||||
|
func GetCompressor(name string) Compressor {
|
||||||
|
return registeredCompressor[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Codec defines the interface gRPC uses to encode and decode messages. Note
|
||||||
|
// that implementations of this interface must be thread safe; a Codec's
|
||||||
|
// methods can be called from concurrent goroutines.
|
||||||
|
type Codec interface {
|
||||||
|
// Marshal returns the wire format of v.
|
||||||
|
Marshal(v any) ([]byte, error)
|
||||||
|
// Unmarshal parses the wire format into v.
|
||||||
|
Unmarshal(data []byte, v any) error
|
||||||
|
// Name returns the name of the Codec implementation. The returned string
|
||||||
|
// will be used as part of content type in transmission. The result must be
|
||||||
|
// static; the result cannot change between calls.
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
var registeredCodecs = make(map[string]any)
|
||||||
|
|
||||||
|
// RegisterCodec registers the provided Codec for use with all gRPC clients and
|
||||||
|
// servers.
|
||||||
|
//
|
||||||
|
// The Codec will be stored and looked up by result of its Name() method, which
|
||||||
|
// should match the content-subtype of the encoding handled by the Codec. This
|
||||||
|
// is case-insensitive, and is stored and looked up as lowercase. If the
|
||||||
|
// result of calling Name() is an empty string, RegisterCodec will panic. See
|
||||||
|
// Content-Type on
|
||||||
|
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for
|
||||||
|
// more details.
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple Codecs are
|
||||||
|
// registered with the same name, the one registered last will take effect.
|
||||||
|
func RegisterCodec(codec Codec) {
|
||||||
|
if codec == nil {
|
||||||
|
panic("cannot register a nil Codec")
|
||||||
|
}
|
||||||
|
if codec.Name() == "" {
|
||||||
|
panic("cannot register Codec with empty string result for Name()")
|
||||||
|
}
|
||||||
|
contentSubtype := strings.ToLower(codec.Name())
|
||||||
|
registeredCodecs[contentSubtype] = codec
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCodec gets a registered Codec by content-subtype, or nil if no Codec is
|
||||||
|
// registered for the content-subtype.
|
||||||
|
//
|
||||||
|
// The content-subtype is expected to be lowercase.
|
||||||
|
func GetCodec(contentSubtype string) Codec {
|
||||||
|
c, _ := registeredCodecs[contentSubtype].(Codec)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 encoding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/mem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CodecV2 defines the interface gRPC uses to encode and decode messages. Note
|
||||||
|
// that implementations of this interface must be thread safe; a CodecV2's
|
||||||
|
// methods can be called from concurrent goroutines.
|
||||||
|
type CodecV2 interface {
|
||||||
|
// Marshal returns the wire format of v. The buffers in the returned
|
||||||
|
// [mem.BufferSlice] must have at least one reference each, which will be freed
|
||||||
|
// by gRPC when they are no longer needed.
|
||||||
|
Marshal(v any) (out mem.BufferSlice, err error)
|
||||||
|
// Unmarshal parses the wire format into v. Note that data will be freed as soon
|
||||||
|
// as this function returns. If the codec wishes to guarantee access to the data
|
||||||
|
// after this function, it must take its own reference that it frees when it is
|
||||||
|
// no longer needed.
|
||||||
|
Unmarshal(data mem.BufferSlice, v any) error
|
||||||
|
// Name returns the name of the Codec implementation. The returned string
|
||||||
|
// will be used as part of content type in transmission. The result must be
|
||||||
|
// static; the result cannot change between calls.
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCodecV2 registers the provided CodecV2 for use with all gRPC clients and
|
||||||
|
// servers.
|
||||||
|
//
|
||||||
|
// The CodecV2 will be stored and looked up by result of its Name() method, which
|
||||||
|
// should match the content-subtype of the encoding handled by the CodecV2. This
|
||||||
|
// is case-insensitive, and is stored and looked up as lowercase. If the
|
||||||
|
// result of calling Name() is an empty string, RegisterCodecV2 will panic. See
|
||||||
|
// Content-Type on
|
||||||
|
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for
|
||||||
|
// more details.
|
||||||
|
//
|
||||||
|
// If both a Codec and CodecV2 are registered with the same name, the CodecV2
|
||||||
|
// will be used.
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple Codecs are
|
||||||
|
// registered with the same name, the one registered last will take effect.
|
||||||
|
func RegisterCodecV2(codec CodecV2) {
|
||||||
|
if codec == nil {
|
||||||
|
panic("cannot register a nil CodecV2")
|
||||||
|
}
|
||||||
|
if codec.Name() == "" {
|
||||||
|
panic("cannot register CodecV2 with empty string result for Name()")
|
||||||
|
}
|
||||||
|
contentSubtype := strings.ToLower(codec.Name())
|
||||||
|
registeredCodecs[contentSubtype] = codec
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCodecV2 gets a registered CodecV2 by content-subtype, or nil if no CodecV2 is
|
||||||
|
// registered for the content-subtype.
|
||||||
|
//
|
||||||
|
// The content-subtype is expected to be lowercase.
|
||||||
|
func GetCodecV2(contentSubtype string) CodecV2 {
|
||||||
|
c, _ := registeredCodecs[contentSubtype].(CodecV2)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2025 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 internal contains code internal to the encoding package.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
// RegisterCompressorForTesting registers a compressor in the global compressor
|
||||||
|
// registry. It returns a cleanup function that should be called at the end
|
||||||
|
// of the test to unregister the compressor.
|
||||||
|
//
|
||||||
|
// This prevents compressors registered in one test from appearing in the
|
||||||
|
// encoding headers of subsequent tests.
|
||||||
|
var RegisterCompressorForTesting any // func RegisterCompressor(c Compressor) func()
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 proto defines the protobuf codec. Importing this package will
|
||||||
|
// register the codec.
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/encoding"
|
||||||
|
"google.golang.org/grpc/mem"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/protoadapt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Name is the name registered for the proto compressor.
|
||||||
|
const Name = "proto"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
encoding.RegisterCodecV2(&codecV2{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// codec is a CodecV2 implementation with protobuf. It is the default codec for
|
||||||
|
// gRPC.
|
||||||
|
type codecV2 struct{}
|
||||||
|
|
||||||
|
func (c *codecV2) Marshal(v any) (data mem.BufferSlice, err error) {
|
||||||
|
vv := messageV2Of(v)
|
||||||
|
if vv == nil {
|
||||||
|
return nil, fmt.Errorf("proto: failed to marshal, message is %T, want proto.Message", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Important: if we remove this Size call then we cannot use
|
||||||
|
// UseCachedSize in MarshalOptions below.
|
||||||
|
size := proto.Size(vv)
|
||||||
|
|
||||||
|
// MarshalOptions with UseCachedSize allows reusing the result from the
|
||||||
|
// previous Size call. This is safe here because:
|
||||||
|
//
|
||||||
|
// 1. We just computed the size.
|
||||||
|
// 2. We assume the message is not being mutated concurrently.
|
||||||
|
//
|
||||||
|
// Important: If the proto.Size call above is removed, using UseCachedSize
|
||||||
|
// becomes unsafe and may lead to incorrect marshaling.
|
||||||
|
//
|
||||||
|
// For more details, see the doc of UseCachedSize:
|
||||||
|
// https://pkg.go.dev/google.golang.org/protobuf/proto#MarshalOptions
|
||||||
|
marshalOptions := proto.MarshalOptions{UseCachedSize: true}
|
||||||
|
|
||||||
|
if mem.IsBelowBufferPoolingThreshold(size) {
|
||||||
|
buf, err := marshalOptions.Marshal(vv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data = append(data, mem.SliceBuffer(buf))
|
||||||
|
} else {
|
||||||
|
pool := mem.DefaultBufferPool()
|
||||||
|
buf := pool.Get(size)
|
||||||
|
if _, err := marshalOptions.MarshalAppend((*buf)[:0], vv); err != nil {
|
||||||
|
pool.Put(buf)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data = append(data, mem.NewBuffer(buf, pool))
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codecV2) Unmarshal(data mem.BufferSlice, v any) (err error) {
|
||||||
|
vv := messageV2Of(v)
|
||||||
|
if vv == nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := data.MaterializeToBuffer(mem.DefaultBufferPool())
|
||||||
|
defer buf.Free()
|
||||||
|
// TODO: Upgrade proto.Unmarshal to support mem.BufferSlice. Right now, it's not
|
||||||
|
// really possible without a major overhaul of the proto package, but the
|
||||||
|
// vtprotobuf library may be able to support this.
|
||||||
|
return proto.Unmarshal(buf.ReadOnlyData(), vv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageV2Of(v any) proto.Message {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case protoadapt.MessageV1:
|
||||||
|
return protoadapt.MessageV2Of(v)
|
||||||
|
case protoadapt.MessageV2:
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codecV2) Name() string {
|
||||||
|
return Name
|
||||||
|
}
|
||||||
342
vendor/google.golang.org/grpc/experimental/stats/metricregistry.go
generated
vendored
Normal file
342
vendor/google.golang.org/grpc/experimental/stats/metricregistry.go
generated
vendored
Normal file
|
|
@ -0,0 +1,342 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 stats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"maps"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
"google.golang.org/grpc/internal"
|
||||||
|
"google.golang.org/grpc/stats"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
internal.SnapshotMetricRegistryForTesting = snapshotMetricsRegistryForTesting
|
||||||
|
}
|
||||||
|
|
||||||
|
var logger = grpclog.Component("metrics-registry")
|
||||||
|
|
||||||
|
// DefaultMetrics are the default metrics registered through global metrics
|
||||||
|
// registry. This is written to at initialization time only, and is read only
|
||||||
|
// after initialization.
|
||||||
|
var DefaultMetrics = stats.NewMetricSet()
|
||||||
|
|
||||||
|
// MetricDescriptor is the data for a registered metric.
|
||||||
|
type MetricDescriptor struct {
|
||||||
|
// The name of this metric. This name must be unique across the whole binary
|
||||||
|
// (including any per call metrics). See
|
||||||
|
// https://github.com/grpc/proposal/blob/master/A79-non-per-call-metrics-architecture.md#metric-instrument-naming-conventions
|
||||||
|
// for metric naming conventions.
|
||||||
|
Name string
|
||||||
|
// The description of this metric.
|
||||||
|
Description string
|
||||||
|
// The unit (e.g. entries, seconds) of this metric.
|
||||||
|
Unit string
|
||||||
|
// The required label keys for this metric. These are intended to
|
||||||
|
// metrics emitted from a stats handler.
|
||||||
|
Labels []string
|
||||||
|
// The optional label keys for this metric. These are intended to attached
|
||||||
|
// to metrics emitted from a stats handler if configured.
|
||||||
|
OptionalLabels []string
|
||||||
|
// Whether this metric is on by default.
|
||||||
|
Default bool
|
||||||
|
// The type of metric. This is set by the metric registry, and not intended
|
||||||
|
// to be set by a component registering a metric.
|
||||||
|
Type MetricType
|
||||||
|
// Bounds are the bounds of this metric. This only applies to histogram
|
||||||
|
// metrics. If unset or set with length 0, stats handlers will fall back to
|
||||||
|
// default bounds.
|
||||||
|
Bounds []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetricType is the type of metric.
|
||||||
|
type MetricType int
|
||||||
|
|
||||||
|
// Type of metric supported by this instrument registry.
|
||||||
|
const (
|
||||||
|
MetricTypeIntCount MetricType = iota
|
||||||
|
MetricTypeFloatCount
|
||||||
|
MetricTypeIntHisto
|
||||||
|
MetricTypeFloatHisto
|
||||||
|
MetricTypeIntGauge
|
||||||
|
MetricTypeIntUpDownCount
|
||||||
|
MetricTypeIntAsyncGauge
|
||||||
|
)
|
||||||
|
|
||||||
|
// Int64CountHandle is a typed handle for a int count metric. This handle
|
||||||
|
// is passed at the recording point in order to know which metric to record
|
||||||
|
// on.
|
||||||
|
type Int64CountHandle MetricDescriptor
|
||||||
|
|
||||||
|
// Descriptor returns the int64 count handle typecast to a pointer to a
|
||||||
|
// MetricDescriptor.
|
||||||
|
func (h *Int64CountHandle) Descriptor() *MetricDescriptor {
|
||||||
|
return (*MetricDescriptor)(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record records the int64 count value on the metrics recorder provided.
|
||||||
|
func (h *Int64CountHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
|
||||||
|
recorder.RecordInt64Count(h, incr, labels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64UpDownCountHandle is a typed handle for an int up-down counter metric.
|
||||||
|
// This handle is passed at the recording point in order to know which metric
|
||||||
|
// to record on.
|
||||||
|
type Int64UpDownCountHandle MetricDescriptor
|
||||||
|
|
||||||
|
// Descriptor returns the int64 up-down counter handle typecast to a pointer to a
|
||||||
|
// MetricDescriptor.
|
||||||
|
func (h *Int64UpDownCountHandle) Descriptor() *MetricDescriptor {
|
||||||
|
return (*MetricDescriptor)(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record records the int64 up-down counter value on the metrics recorder provided.
|
||||||
|
// The value 'v' can be positive to increment or negative to decrement.
|
||||||
|
func (h *Int64UpDownCountHandle) Record(recorder MetricsRecorder, v int64, labels ...string) {
|
||||||
|
recorder.RecordInt64UpDownCount(h, v, labels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64CountHandle is a typed handle for a float count metric. This handle is
|
||||||
|
// passed at the recording point in order to know which metric to record on.
|
||||||
|
type Float64CountHandle MetricDescriptor
|
||||||
|
|
||||||
|
// Descriptor returns the float64 count handle typecast to a pointer to a
|
||||||
|
// MetricDescriptor.
|
||||||
|
func (h *Float64CountHandle) Descriptor() *MetricDescriptor {
|
||||||
|
return (*MetricDescriptor)(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record records the float64 count value on the metrics recorder provided.
|
||||||
|
func (h *Float64CountHandle) Record(recorder MetricsRecorder, incr float64, labels ...string) {
|
||||||
|
recorder.RecordFloat64Count(h, incr, labels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64HistoHandle is a typed handle for an int histogram metric. This handle
|
||||||
|
// is passed at the recording point in order to know which metric to record on.
|
||||||
|
type Int64HistoHandle MetricDescriptor
|
||||||
|
|
||||||
|
// Descriptor returns the int64 histo handle typecast to a pointer to a
|
||||||
|
// MetricDescriptor.
|
||||||
|
func (h *Int64HistoHandle) Descriptor() *MetricDescriptor {
|
||||||
|
return (*MetricDescriptor)(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record records the int64 histo value on the metrics recorder provided.
|
||||||
|
func (h *Int64HistoHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
|
||||||
|
recorder.RecordInt64Histo(h, incr, labels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64HistoHandle is a typed handle for a float histogram metric. This
|
||||||
|
// handle is passed at the recording point in order to know which metric to
|
||||||
|
// record on.
|
||||||
|
type Float64HistoHandle MetricDescriptor
|
||||||
|
|
||||||
|
// Descriptor returns the float64 histo handle typecast to a pointer to a
|
||||||
|
// MetricDescriptor.
|
||||||
|
func (h *Float64HistoHandle) Descriptor() *MetricDescriptor {
|
||||||
|
return (*MetricDescriptor)(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record records the float64 histo value on the metrics recorder provided.
|
||||||
|
func (h *Float64HistoHandle) Record(recorder MetricsRecorder, incr float64, labels ...string) {
|
||||||
|
recorder.RecordFloat64Histo(h, incr, labels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64GaugeHandle is a typed handle for an int gauge metric. This handle is
|
||||||
|
// passed at the recording point in order to know which metric to record on.
|
||||||
|
type Int64GaugeHandle MetricDescriptor
|
||||||
|
|
||||||
|
// Descriptor returns the int64 gauge handle typecast to a pointer to a
|
||||||
|
// MetricDescriptor.
|
||||||
|
func (h *Int64GaugeHandle) Descriptor() *MetricDescriptor {
|
||||||
|
return (*MetricDescriptor)(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record records the int64 histo value on the metrics recorder provided.
|
||||||
|
func (h *Int64GaugeHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
|
||||||
|
recorder.RecordInt64Gauge(h, incr, labels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncMetric is a marker interface for asynchronous metric types.
|
||||||
|
type AsyncMetric interface {
|
||||||
|
isAsync()
|
||||||
|
Descriptor() *MetricDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64AsyncGaugeHandle is a typed handle for an int gauge metric. This handle is
|
||||||
|
// passed at the recording point in order to know which metric to record on.
|
||||||
|
type Int64AsyncGaugeHandle MetricDescriptor
|
||||||
|
|
||||||
|
// isAsync implements the AsyncMetric interface.
|
||||||
|
func (h *Int64AsyncGaugeHandle) isAsync() {}
|
||||||
|
|
||||||
|
// Descriptor returns the int64 gauge handle typecast to a pointer to a
|
||||||
|
// MetricDescriptor.
|
||||||
|
func (h *Int64AsyncGaugeHandle) Descriptor() *MetricDescriptor {
|
||||||
|
return (*MetricDescriptor)(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record records the int64 gauge value on the metrics recorder provided.
|
||||||
|
func (h *Int64AsyncGaugeHandle) Record(recorder AsyncMetricsRecorder, value int64, labels ...string) {
|
||||||
|
recorder.RecordInt64AsyncGauge(h, value, labels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// registeredMetrics are the registered metric descriptor names.
|
||||||
|
var registeredMetrics = make(map[string]bool)
|
||||||
|
|
||||||
|
// metricsRegistry contains all of the registered metrics.
|
||||||
|
//
|
||||||
|
// This is written to only at init time, and read only after that.
|
||||||
|
var metricsRegistry = make(map[string]*MetricDescriptor)
|
||||||
|
|
||||||
|
// DescriptorForMetric returns the MetricDescriptor from the global registry.
|
||||||
|
//
|
||||||
|
// Returns nil if MetricDescriptor not present.
|
||||||
|
func DescriptorForMetric(metricName string) *MetricDescriptor {
|
||||||
|
return metricsRegistry[metricName]
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerMetric(metricName string, def bool) {
|
||||||
|
if registeredMetrics[metricName] {
|
||||||
|
logger.Fatalf("metric %v already registered", metricName)
|
||||||
|
}
|
||||||
|
registeredMetrics[metricName] = true
|
||||||
|
if def {
|
||||||
|
DefaultMetrics = DefaultMetrics.Add(metricName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterInt64Count registers the metric description onto the global registry.
|
||||||
|
// It returns a typed handle to use to recording data.
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple metrics are
|
||||||
|
// registered with the same name, this function will panic.
|
||||||
|
func RegisterInt64Count(descriptor MetricDescriptor) *Int64CountHandle {
|
||||||
|
registerMetric(descriptor.Name, descriptor.Default)
|
||||||
|
descriptor.Type = MetricTypeIntCount
|
||||||
|
descPtr := &descriptor
|
||||||
|
metricsRegistry[descriptor.Name] = descPtr
|
||||||
|
return (*Int64CountHandle)(descPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFloat64Count registers the metric description onto the global
|
||||||
|
// registry. It returns a typed handle to use to recording data.
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple metrics are
|
||||||
|
// registered with the same name, this function will panic.
|
||||||
|
func RegisterFloat64Count(descriptor MetricDescriptor) *Float64CountHandle {
|
||||||
|
registerMetric(descriptor.Name, descriptor.Default)
|
||||||
|
descriptor.Type = MetricTypeFloatCount
|
||||||
|
descPtr := &descriptor
|
||||||
|
metricsRegistry[descriptor.Name] = descPtr
|
||||||
|
return (*Float64CountHandle)(descPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterInt64Histo registers the metric description onto the global registry.
|
||||||
|
// It returns a typed handle to use to recording data.
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple metrics are
|
||||||
|
// registered with the same name, this function will panic.
|
||||||
|
func RegisterInt64Histo(descriptor MetricDescriptor) *Int64HistoHandle {
|
||||||
|
registerMetric(descriptor.Name, descriptor.Default)
|
||||||
|
descriptor.Type = MetricTypeIntHisto
|
||||||
|
descPtr := &descriptor
|
||||||
|
metricsRegistry[descriptor.Name] = descPtr
|
||||||
|
return (*Int64HistoHandle)(descPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFloat64Histo registers the metric description onto the global
|
||||||
|
// registry. It returns a typed handle to use to recording data.
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple metrics are
|
||||||
|
// registered with the same name, this function will panic.
|
||||||
|
func RegisterFloat64Histo(descriptor MetricDescriptor) *Float64HistoHandle {
|
||||||
|
registerMetric(descriptor.Name, descriptor.Default)
|
||||||
|
descriptor.Type = MetricTypeFloatHisto
|
||||||
|
descPtr := &descriptor
|
||||||
|
metricsRegistry[descriptor.Name] = descPtr
|
||||||
|
return (*Float64HistoHandle)(descPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterInt64Gauge registers the metric description onto the global registry.
|
||||||
|
// It returns a typed handle to use to recording data.
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple metrics are
|
||||||
|
// registered with the same name, this function will panic.
|
||||||
|
func RegisterInt64Gauge(descriptor MetricDescriptor) *Int64GaugeHandle {
|
||||||
|
registerMetric(descriptor.Name, descriptor.Default)
|
||||||
|
descriptor.Type = MetricTypeIntGauge
|
||||||
|
descPtr := &descriptor
|
||||||
|
metricsRegistry[descriptor.Name] = descPtr
|
||||||
|
return (*Int64GaugeHandle)(descPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterInt64UpDownCount registers the metric description onto the global registry.
|
||||||
|
// It returns a typed handle to use for recording data.
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple metrics are
|
||||||
|
// registered with the same name, this function will panic.
|
||||||
|
func RegisterInt64UpDownCount(descriptor MetricDescriptor) *Int64UpDownCountHandle {
|
||||||
|
registerMetric(descriptor.Name, descriptor.Default)
|
||||||
|
// Set the specific metric type for the up-down counter
|
||||||
|
descriptor.Type = MetricTypeIntUpDownCount
|
||||||
|
descPtr := &descriptor
|
||||||
|
metricsRegistry[descriptor.Name] = descPtr
|
||||||
|
return (*Int64UpDownCountHandle)(descPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterInt64AsyncGauge registers the metric description onto the global registry.
|
||||||
|
// It returns a typed handle to use for recording data.
|
||||||
|
//
|
||||||
|
// NOTE: this function must only be called during initialization time (i.e. in
|
||||||
|
// an init() function), and is not thread-safe. If multiple metrics are
|
||||||
|
// registered with the same name, this function will panic.
|
||||||
|
func RegisterInt64AsyncGauge(descriptor MetricDescriptor) *Int64AsyncGaugeHandle {
|
||||||
|
registerMetric(descriptor.Name, descriptor.Default)
|
||||||
|
descriptor.Type = MetricTypeIntAsyncGauge
|
||||||
|
descPtr := &descriptor
|
||||||
|
metricsRegistry[descriptor.Name] = descPtr
|
||||||
|
return (*Int64AsyncGaugeHandle)(descPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// snapshotMetricsRegistryForTesting snapshots the global data of the metrics
|
||||||
|
// registry. Returns a cleanup function that sets the metrics registry to its
|
||||||
|
// original state.
|
||||||
|
func snapshotMetricsRegistryForTesting() func() {
|
||||||
|
oldDefaultMetrics := DefaultMetrics
|
||||||
|
oldRegisteredMetrics := registeredMetrics
|
||||||
|
oldMetricsRegistry := metricsRegistry
|
||||||
|
|
||||||
|
registeredMetrics = make(map[string]bool)
|
||||||
|
metricsRegistry = make(map[string]*MetricDescriptor)
|
||||||
|
maps.Copy(registeredMetrics, registeredMetrics)
|
||||||
|
maps.Copy(metricsRegistry, metricsRegistry)
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
DefaultMetrics = oldDefaultMetrics
|
||||||
|
registeredMetrics = oldRegisteredMetrics
|
||||||
|
metricsRegistry = oldMetricsRegistry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 stats contains experimental metrics/stats API's.
|
||||||
|
package stats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc/internal"
|
||||||
|
"google.golang.org/grpc/stats"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MetricsRecorder records on metrics derived from metric registry.
|
||||||
|
// Implementors must embed UnimplementedMetricsRecorder.
|
||||||
|
type MetricsRecorder interface {
|
||||||
|
// RecordInt64Count records the measurement alongside labels on the int
|
||||||
|
// count associated with the provided handle.
|
||||||
|
RecordInt64Count(handle *Int64CountHandle, incr int64, labels ...string)
|
||||||
|
// RecordFloat64Count records the measurement alongside labels on the float
|
||||||
|
// count associated with the provided handle.
|
||||||
|
RecordFloat64Count(handle *Float64CountHandle, incr float64, labels ...string)
|
||||||
|
// RecordInt64Histo records the measurement alongside labels on the int
|
||||||
|
// histo associated with the provided handle.
|
||||||
|
RecordInt64Histo(handle *Int64HistoHandle, incr int64, labels ...string)
|
||||||
|
// RecordFloat64Histo records the measurement alongside labels on the float
|
||||||
|
// histo associated with the provided handle.
|
||||||
|
RecordFloat64Histo(handle *Float64HistoHandle, incr float64, labels ...string)
|
||||||
|
// RecordInt64Gauge records the measurement alongside labels on the int
|
||||||
|
// gauge associated with the provided handle.
|
||||||
|
RecordInt64Gauge(handle *Int64GaugeHandle, incr int64, labels ...string)
|
||||||
|
// RecordInt64UpDownCounter records the measurement alongside labels on the int
|
||||||
|
// count associated with the provided handle.
|
||||||
|
RecordInt64UpDownCount(handle *Int64UpDownCountHandle, incr int64, labels ...string)
|
||||||
|
// RegisterAsyncReporter registers a reporter to produce metric values for
|
||||||
|
// only the listed descriptors. The returned function must be called when
|
||||||
|
// the metrics are no longer needed, which will remove the reporter. The
|
||||||
|
// returned method needs to be idempotent and concurrent safe.
|
||||||
|
RegisterAsyncReporter(reporter AsyncMetricReporter, descriptors ...AsyncMetric) func()
|
||||||
|
|
||||||
|
// EnforceMetricsRecorderEmbedding is included to force implementers to embed
|
||||||
|
// another implementation of this interface, allowing gRPC to add methods
|
||||||
|
// without breaking users.
|
||||||
|
internal.EnforceMetricsRecorderEmbedding
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncMetricReporter is an interface for types that record metrics asynchronously
|
||||||
|
// for the set of descriptors they are registered with. The AsyncMetricsRecorder
|
||||||
|
// parameter is used to record values for these metrics.
|
||||||
|
//
|
||||||
|
// Implementations must make unique recordings across all registered
|
||||||
|
// AsyncMetricReporters. Meaning, they should not report values for a metric with
|
||||||
|
// the same attributes as another AsyncMetricReporter will report.
|
||||||
|
//
|
||||||
|
// Implementations must be concurrent-safe.
|
||||||
|
type AsyncMetricReporter interface {
|
||||||
|
// Report records metric values using the provided recorder.
|
||||||
|
Report(AsyncMetricsRecorder) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncMetricReporterFunc is an adapter to allow the use of ordinary functions as
|
||||||
|
// AsyncMetricReporters.
|
||||||
|
type AsyncMetricReporterFunc func(AsyncMetricsRecorder) error
|
||||||
|
|
||||||
|
// Report calls f(r).
|
||||||
|
func (f AsyncMetricReporterFunc) Report(r AsyncMetricsRecorder) error {
|
||||||
|
return f(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncMetricsRecorder records on asynchronous metrics derived from metric registry.
|
||||||
|
type AsyncMetricsRecorder interface {
|
||||||
|
// RecordInt64AsyncGauge records the measurement alongside labels on the int
|
||||||
|
// count associated with the provided handle asynchronously
|
||||||
|
RecordInt64AsyncGauge(handle *Int64AsyncGaugeHandle, incr int64, labels ...string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metrics is an experimental legacy alias of the now-stable stats.MetricSet.
|
||||||
|
// Metrics will be deleted in a future release.
|
||||||
|
type Metrics = stats.MetricSet
|
||||||
|
|
||||||
|
// Metric was replaced by direct usage of strings.
|
||||||
|
type Metric = string
|
||||||
|
|
||||||
|
// NewMetrics is an experimental legacy alias of the now-stable
|
||||||
|
// stats.NewMetricSet. NewMetrics will be deleted in a future release.
|
||||||
|
func NewMetrics(metrics ...Metric) *Metrics {
|
||||||
|
return stats.NewMetricSet(metrics...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedMetricsRecorder must be embedded to have forward compatible implementations.
|
||||||
|
type UnimplementedMetricsRecorder struct {
|
||||||
|
internal.EnforceMetricsRecorderEmbedding
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordInt64Count provides a no-op implementation.
|
||||||
|
func (UnimplementedMetricsRecorder) RecordInt64Count(*Int64CountHandle, int64, ...string) {}
|
||||||
|
|
||||||
|
// RecordFloat64Count provides a no-op implementation.
|
||||||
|
func (UnimplementedMetricsRecorder) RecordFloat64Count(*Float64CountHandle, float64, ...string) {}
|
||||||
|
|
||||||
|
// RecordInt64Histo provides a no-op implementation.
|
||||||
|
func (UnimplementedMetricsRecorder) RecordInt64Histo(*Int64HistoHandle, int64, ...string) {}
|
||||||
|
|
||||||
|
// RecordFloat64Histo provides a no-op implementation.
|
||||||
|
func (UnimplementedMetricsRecorder) RecordFloat64Histo(*Float64HistoHandle, float64, ...string) {}
|
||||||
|
|
||||||
|
// RecordInt64Gauge provides a no-op implementation.
|
||||||
|
func (UnimplementedMetricsRecorder) RecordInt64Gauge(*Int64GaugeHandle, int64, ...string) {}
|
||||||
|
|
||||||
|
// RecordInt64UpDownCount provides a no-op implementation.
|
||||||
|
func (UnimplementedMetricsRecorder) RecordInt64UpDownCount(*Int64UpDownCountHandle, int64, ...string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterAsyncReporter provides a no-op implementation.
|
||||||
|
func (UnimplementedMetricsRecorder) RegisterAsyncReporter(AsyncMetricReporter, ...AsyncMetric) func() {
|
||||||
|
// No-op: Return an empty function to ensure caller doesn't panic on nil function call
|
||||||
|
return func() {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 grpclog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// componentData records the settings for a component.
|
||||||
|
type componentData struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var cache = map[string]*componentData{}
|
||||||
|
|
||||||
|
func (c *componentData) InfoDepth(depth int, args ...any) {
|
||||||
|
args = append([]any{"[" + string(c.name) + "]"}, args...)
|
||||||
|
InfoDepth(depth+1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) WarningDepth(depth int, args ...any) {
|
||||||
|
args = append([]any{"[" + string(c.name) + "]"}, args...)
|
||||||
|
WarningDepth(depth+1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) ErrorDepth(depth int, args ...any) {
|
||||||
|
args = append([]any{"[" + string(c.name) + "]"}, args...)
|
||||||
|
ErrorDepth(depth+1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) FatalDepth(depth int, args ...any) {
|
||||||
|
args = append([]any{"[" + string(c.name) + "]"}, args...)
|
||||||
|
FatalDepth(depth+1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Info(args ...any) {
|
||||||
|
c.InfoDepth(1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Warning(args ...any) {
|
||||||
|
c.WarningDepth(1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Error(args ...any) {
|
||||||
|
c.ErrorDepth(1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Fatal(args ...any) {
|
||||||
|
c.FatalDepth(1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Infof(format string, args ...any) {
|
||||||
|
c.InfoDepth(1, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Warningf(format string, args ...any) {
|
||||||
|
c.WarningDepth(1, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Errorf(format string, args ...any) {
|
||||||
|
c.ErrorDepth(1, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Fatalf(format string, args ...any) {
|
||||||
|
c.FatalDepth(1, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Infoln(args ...any) {
|
||||||
|
c.InfoDepth(1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Warningln(args ...any) {
|
||||||
|
c.WarningDepth(1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Errorln(args ...any) {
|
||||||
|
c.ErrorDepth(1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) Fatalln(args ...any) {
|
||||||
|
c.FatalDepth(1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *componentData) V(l int) bool {
|
||||||
|
return V(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component creates a new component and returns it for logging. If a component
|
||||||
|
// with the name already exists, nothing will be created and it will be
|
||||||
|
// returned. SetLoggerV2 will panic if it is called with a logger created by
|
||||||
|
// Component.
|
||||||
|
func Component(componentName string) DepthLoggerV2 {
|
||||||
|
if cData, ok := cache[componentName]; ok {
|
||||||
|
return cData
|
||||||
|
}
|
||||||
|
c := &componentData{componentName}
|
||||||
|
cache[componentName] = c
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 grpclog defines logging for grpc.
|
||||||
|
//
|
||||||
|
// In the default logger, severity level can be set by environment variable
|
||||||
|
// GRPC_GO_LOG_SEVERITY_LEVEL, verbosity level can be set by
|
||||||
|
// GRPC_GO_LOG_VERBOSITY_LEVEL.
|
||||||
|
package grpclog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/grpclog/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SetLoggerV2(newLoggerV2())
|
||||||
|
}
|
||||||
|
|
||||||
|
// V reports whether verbosity level l is at least the requested verbose level.
|
||||||
|
func V(l int) bool {
|
||||||
|
return internal.LoggerV2Impl.V(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs to the INFO log.
|
||||||
|
func Info(args ...any) {
|
||||||
|
internal.LoggerV2Impl.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs to the INFO log. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
func Infof(format string, args ...any) {
|
||||||
|
internal.LoggerV2Impl.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infoln logs to the INFO log. Arguments are handled in the manner of fmt.Println.
|
||||||
|
func Infoln(args ...any) {
|
||||||
|
internal.LoggerV2Impl.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs to the WARNING log.
|
||||||
|
func Warning(args ...any) {
|
||||||
|
internal.LoggerV2Impl.Warning(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf logs to the WARNING log. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
func Warningf(format string, args ...any) {
|
||||||
|
internal.LoggerV2Impl.Warningf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningln logs to the WARNING log. Arguments are handled in the manner of fmt.Println.
|
||||||
|
func Warningln(args ...any) {
|
||||||
|
internal.LoggerV2Impl.Warningln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs to the ERROR log.
|
||||||
|
func Error(args ...any) {
|
||||||
|
internal.LoggerV2Impl.Error(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs to the ERROR log. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
func Errorf(format string, args ...any) {
|
||||||
|
internal.LoggerV2Impl.Errorf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorln logs to the ERROR log. Arguments are handled in the manner of fmt.Println.
|
||||||
|
func Errorln(args ...any) {
|
||||||
|
internal.LoggerV2Impl.Errorln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs to the FATAL log. Arguments are handled in the manner of fmt.Print.
|
||||||
|
// It calls os.Exit() with exit code 1.
|
||||||
|
func Fatal(args ...any) {
|
||||||
|
internal.LoggerV2Impl.Fatal(args...)
|
||||||
|
// Make sure fatal logs will exit.
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs to the FATAL log. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
// It calls os.Exit() with exit code 1.
|
||||||
|
func Fatalf(format string, args ...any) {
|
||||||
|
internal.LoggerV2Impl.Fatalf(format, args...)
|
||||||
|
// Make sure fatal logs will exit.
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalln logs to the FATAL log. Arguments are handled in the manner of fmt.Println.
|
||||||
|
// It calls os.Exit() with exit code 1.
|
||||||
|
func Fatalln(args ...any) {
|
||||||
|
internal.LoggerV2Impl.Fatalln(args...)
|
||||||
|
// Make sure fatal logs will exit.
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print prints to the logger. Arguments are handled in the manner of fmt.Print.
|
||||||
|
//
|
||||||
|
// Deprecated: use Info.
|
||||||
|
func Print(args ...any) {
|
||||||
|
internal.LoggerV2Impl.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf prints to the logger. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
//
|
||||||
|
// Deprecated: use Infof.
|
||||||
|
func Printf(format string, args ...any) {
|
||||||
|
internal.LoggerV2Impl.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println prints to the logger. Arguments are handled in the manner of fmt.Println.
|
||||||
|
//
|
||||||
|
// Deprecated: use Infoln.
|
||||||
|
func Println(args ...any) {
|
||||||
|
internal.LoggerV2Impl.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoDepth logs to the INFO log at the specified depth.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func InfoDepth(depth int, args ...any) {
|
||||||
|
if internal.DepthLoggerV2Impl != nil {
|
||||||
|
internal.DepthLoggerV2Impl.InfoDepth(depth, args...)
|
||||||
|
} else {
|
||||||
|
internal.LoggerV2Impl.Infoln(args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WarningDepth logs to the WARNING log at the specified depth.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func WarningDepth(depth int, args ...any) {
|
||||||
|
if internal.DepthLoggerV2Impl != nil {
|
||||||
|
internal.DepthLoggerV2Impl.WarningDepth(depth, args...)
|
||||||
|
} else {
|
||||||
|
internal.LoggerV2Impl.Warningln(args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorDepth logs to the ERROR log at the specified depth.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func ErrorDepth(depth int, args ...any) {
|
||||||
|
if internal.DepthLoggerV2Impl != nil {
|
||||||
|
internal.DepthLoggerV2Impl.ErrorDepth(depth, args...)
|
||||||
|
} else {
|
||||||
|
internal.LoggerV2Impl.Errorln(args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalDepth logs to the FATAL log at the specified depth.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
func FatalDepth(depth int, args ...any) {
|
||||||
|
if internal.DepthLoggerV2Impl != nil {
|
||||||
|
internal.DepthLoggerV2Impl.FatalDepth(depth, args...)
|
||||||
|
} else {
|
||||||
|
internal.LoggerV2Impl.Fatalln(args...)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 internal contains functionality internal to the grpclog package.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
// LoggerV2Impl is the logger used for the non-depth log functions.
|
||||||
|
var LoggerV2Impl LoggerV2
|
||||||
|
|
||||||
|
// DepthLoggerV2Impl is the logger used for the depth log functions.
|
||||||
|
var DepthLoggerV2Impl DepthLoggerV2
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 internal
|
||||||
|
|
||||||
|
// Logger mimics golang's standard Logger as an interface.
|
||||||
|
//
|
||||||
|
// Deprecated: use LoggerV2.
|
||||||
|
type Logger interface {
|
||||||
|
Fatal(args ...any)
|
||||||
|
Fatalf(format string, args ...any)
|
||||||
|
Fatalln(args ...any)
|
||||||
|
Print(args ...any)
|
||||||
|
Printf(format string, args ...any)
|
||||||
|
Println(args ...any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerWrapper wraps Logger into a LoggerV2.
|
||||||
|
type LoggerWrapper struct {
|
||||||
|
Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs to INFO log. Arguments are handled in the manner of fmt.Print.
|
||||||
|
func (l *LoggerWrapper) Info(args ...any) {
|
||||||
|
l.Logger.Print(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infoln logs to INFO log. Arguments are handled in the manner of fmt.Println.
|
||||||
|
func (l *LoggerWrapper) Infoln(args ...any) {
|
||||||
|
l.Logger.Println(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
func (l *LoggerWrapper) Infof(format string, args ...any) {
|
||||||
|
l.Logger.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs to WARNING log. Arguments are handled in the manner of fmt.Print.
|
||||||
|
func (l *LoggerWrapper) Warning(args ...any) {
|
||||||
|
l.Logger.Print(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningln logs to WARNING log. Arguments are handled in the manner of fmt.Println.
|
||||||
|
func (l *LoggerWrapper) Warningln(args ...any) {
|
||||||
|
l.Logger.Println(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf logs to WARNING log. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
func (l *LoggerWrapper) Warningf(format string, args ...any) {
|
||||||
|
l.Logger.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs to ERROR log. Arguments are handled in the manner of fmt.Print.
|
||||||
|
func (l *LoggerWrapper) Error(args ...any) {
|
||||||
|
l.Logger.Print(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorln logs to ERROR log. Arguments are handled in the manner of fmt.Println.
|
||||||
|
func (l *LoggerWrapper) Errorln(args ...any) {
|
||||||
|
l.Logger.Println(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
func (l *LoggerWrapper) Errorf(format string, args ...any) {
|
||||||
|
l.Logger.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// V reports whether verbosity level l is at least the requested verbose level.
|
||||||
|
func (*LoggerWrapper) V(int) bool {
|
||||||
|
// Returns true for all verbose level.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,267 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoggerV2 does underlying logging work for grpclog.
|
||||||
|
type LoggerV2 interface {
|
||||||
|
// Info logs to INFO log. Arguments are handled in the manner of fmt.Print.
|
||||||
|
Info(args ...any)
|
||||||
|
// Infoln logs to INFO log. Arguments are handled in the manner of fmt.Println.
|
||||||
|
Infoln(args ...any)
|
||||||
|
// Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
Infof(format string, args ...any)
|
||||||
|
// Warning logs to WARNING log. Arguments are handled in the manner of fmt.Print.
|
||||||
|
Warning(args ...any)
|
||||||
|
// Warningln logs to WARNING log. Arguments are handled in the manner of fmt.Println.
|
||||||
|
Warningln(args ...any)
|
||||||
|
// Warningf logs to WARNING log. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
Warningf(format string, args ...any)
|
||||||
|
// Error logs to ERROR log. Arguments are handled in the manner of fmt.Print.
|
||||||
|
Error(args ...any)
|
||||||
|
// Errorln logs to ERROR log. Arguments are handled in the manner of fmt.Println.
|
||||||
|
Errorln(args ...any)
|
||||||
|
// Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
Errorf(format string, args ...any)
|
||||||
|
// Fatal logs to ERROR log. Arguments are handled in the manner of fmt.Print.
|
||||||
|
// gRPC ensures that all Fatal logs will exit with os.Exit(1).
|
||||||
|
// Implementations may also call os.Exit() with a non-zero exit code.
|
||||||
|
Fatal(args ...any)
|
||||||
|
// Fatalln logs to ERROR log. Arguments are handled in the manner of fmt.Println.
|
||||||
|
// gRPC ensures that all Fatal logs will exit with os.Exit(1).
|
||||||
|
// Implementations may also call os.Exit() with a non-zero exit code.
|
||||||
|
Fatalln(args ...any)
|
||||||
|
// Fatalf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
|
||||||
|
// gRPC ensures that all Fatal logs will exit with os.Exit(1).
|
||||||
|
// Implementations may also call os.Exit() with a non-zero exit code.
|
||||||
|
Fatalf(format string, args ...any)
|
||||||
|
// V reports whether verbosity level l is at least the requested verbose level.
|
||||||
|
V(l int) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DepthLoggerV2 logs at a specified call frame. If a LoggerV2 also implements
|
||||||
|
// DepthLoggerV2, the below functions will be called with the appropriate stack
|
||||||
|
// depth set for trivial functions the logger may ignore.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
type DepthLoggerV2 interface {
|
||||||
|
LoggerV2
|
||||||
|
// InfoDepth logs to INFO log at the specified depth. Arguments are handled in the manner of fmt.Println.
|
||||||
|
InfoDepth(depth int, args ...any)
|
||||||
|
// WarningDepth logs to WARNING log at the specified depth. Arguments are handled in the manner of fmt.Println.
|
||||||
|
WarningDepth(depth int, args ...any)
|
||||||
|
// ErrorDepth logs to ERROR log at the specified depth. Arguments are handled in the manner of fmt.Println.
|
||||||
|
ErrorDepth(depth int, args ...any)
|
||||||
|
// FatalDepth logs to FATAL log at the specified depth. Arguments are handled in the manner of fmt.Println.
|
||||||
|
FatalDepth(depth int, args ...any)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// infoLog indicates Info severity.
|
||||||
|
infoLog int = iota
|
||||||
|
// warningLog indicates Warning severity.
|
||||||
|
warningLog
|
||||||
|
// errorLog indicates Error severity.
|
||||||
|
errorLog
|
||||||
|
// fatalLog indicates Fatal severity.
|
||||||
|
fatalLog
|
||||||
|
)
|
||||||
|
|
||||||
|
// severityName contains the string representation of each severity.
|
||||||
|
var severityName = []string{
|
||||||
|
infoLog: "INFO",
|
||||||
|
warningLog: "WARNING",
|
||||||
|
errorLog: "ERROR",
|
||||||
|
fatalLog: "FATAL",
|
||||||
|
}
|
||||||
|
|
||||||
|
// sprintf is fmt.Sprintf.
|
||||||
|
// These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
|
||||||
|
var sprintf = fmt.Sprintf
|
||||||
|
|
||||||
|
// sprint is fmt.Sprint.
|
||||||
|
// These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
|
||||||
|
var sprint = fmt.Sprint
|
||||||
|
|
||||||
|
// sprintln is fmt.Sprintln.
|
||||||
|
// These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
|
||||||
|
var sprintln = fmt.Sprintln
|
||||||
|
|
||||||
|
// exit is os.Exit.
|
||||||
|
// This var exists to make it possible to test functions calling os.Exit.
|
||||||
|
var exit = os.Exit
|
||||||
|
|
||||||
|
// loggerT is the default logger used by grpclog.
|
||||||
|
type loggerT struct {
|
||||||
|
m []*log.Logger
|
||||||
|
v int
|
||||||
|
jsonFormat bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) output(severity int, s string) {
|
||||||
|
sevStr := severityName[severity]
|
||||||
|
if !g.jsonFormat {
|
||||||
|
g.m[severity].Output(2, sevStr+": "+s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: we can also include the logging component, but that needs more
|
||||||
|
// (API) changes.
|
||||||
|
b, _ := json.Marshal(map[string]string{
|
||||||
|
"severity": sevStr,
|
||||||
|
"message": s,
|
||||||
|
})
|
||||||
|
g.m[severity].Output(2, string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) printf(severity int, format string, args ...any) {
|
||||||
|
// Note the discard check is duplicated in each print func, rather than in
|
||||||
|
// output, to avoid the expensive Sprint calls.
|
||||||
|
// De-duplicating this by moving to output would be a significant performance regression!
|
||||||
|
if lg := g.m[severity]; lg.Writer() == io.Discard {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.output(severity, sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) print(severity int, v ...any) {
|
||||||
|
if lg := g.m[severity]; lg.Writer() == io.Discard {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.output(severity, sprint(v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) println(severity int, v ...any) {
|
||||||
|
if lg := g.m[severity]; lg.Writer() == io.Discard {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.output(severity, sprintln(v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Info(args ...any) {
|
||||||
|
g.print(infoLog, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Infoln(args ...any) {
|
||||||
|
g.println(infoLog, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Infof(format string, args ...any) {
|
||||||
|
g.printf(infoLog, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Warning(args ...any) {
|
||||||
|
g.print(warningLog, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Warningln(args ...any) {
|
||||||
|
g.println(warningLog, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Warningf(format string, args ...any) {
|
||||||
|
g.printf(warningLog, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Error(args ...any) {
|
||||||
|
g.print(errorLog, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Errorln(args ...any) {
|
||||||
|
g.println(errorLog, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Errorf(format string, args ...any) {
|
||||||
|
g.printf(errorLog, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Fatal(args ...any) {
|
||||||
|
g.print(fatalLog, args...)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Fatalln(args ...any) {
|
||||||
|
g.println(fatalLog, args...)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) Fatalf(format string, args ...any) {
|
||||||
|
g.printf(fatalLog, format, args...)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *loggerT) V(l int) bool {
|
||||||
|
return l <= g.v
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerV2Config configures the LoggerV2 implementation.
|
||||||
|
type LoggerV2Config struct {
|
||||||
|
// Verbosity sets the verbosity level of the logger.
|
||||||
|
Verbosity int
|
||||||
|
// FormatJSON controls whether the logger should output logs in JSON format.
|
||||||
|
FormatJSON bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// combineLoggers returns a combined logger for both higher & lower severity logs,
|
||||||
|
// or only one if the other is io.Discard.
|
||||||
|
//
|
||||||
|
// This uses io.Discard instead of io.MultiWriter when all loggers
|
||||||
|
// are set to io.Discard. Both this package and the standard log package have
|
||||||
|
// significant optimizations for io.Discard, which io.MultiWriter lacks (as of
|
||||||
|
// this writing).
|
||||||
|
func combineLoggers(lower, higher io.Writer) io.Writer {
|
||||||
|
if lower == io.Discard {
|
||||||
|
return higher
|
||||||
|
}
|
||||||
|
if higher == io.Discard {
|
||||||
|
return lower
|
||||||
|
}
|
||||||
|
return io.MultiWriter(lower, higher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLoggerV2 creates a new LoggerV2 instance with the provided configuration.
|
||||||
|
// The infoW, warningW, and errorW writers are used to write log messages of
|
||||||
|
// different severity levels.
|
||||||
|
func NewLoggerV2(infoW, warningW, errorW io.Writer, c LoggerV2Config) LoggerV2 {
|
||||||
|
flag := log.LstdFlags
|
||||||
|
if c.FormatJSON {
|
||||||
|
flag = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
warningW = combineLoggers(infoW, warningW)
|
||||||
|
errorW = combineLoggers(errorW, warningW)
|
||||||
|
|
||||||
|
fatalW := errorW
|
||||||
|
|
||||||
|
m := []*log.Logger{
|
||||||
|
log.New(infoW, "", flag),
|
||||||
|
log.New(warningW, "", flag),
|
||||||
|
log.New(errorW, "", flag),
|
||||||
|
log.New(fatalW, "", flag),
|
||||||
|
}
|
||||||
|
return &loggerT{m: m, v: c.Verbosity, jsonFormat: c.FormatJSON}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2015 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 grpclog
|
||||||
|
|
||||||
|
import "google.golang.org/grpc/grpclog/internal"
|
||||||
|
|
||||||
|
// Logger mimics golang's standard Logger as an interface.
|
||||||
|
//
|
||||||
|
// Deprecated: use LoggerV2.
|
||||||
|
type Logger internal.Logger
|
||||||
|
|
||||||
|
// SetLogger sets the logger that is used in grpc. Call only from
|
||||||
|
// init() functions.
|
||||||
|
//
|
||||||
|
// Deprecated: use SetLoggerV2.
|
||||||
|
func SetLogger(l Logger) {
|
||||||
|
internal.LoggerV2Impl = &internal.LoggerWrapper{Logger: l}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 grpclog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/grpclog/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoggerV2 does underlying logging work for grpclog.
|
||||||
|
type LoggerV2 internal.LoggerV2
|
||||||
|
|
||||||
|
// SetLoggerV2 sets logger that is used in grpc to a V2 logger.
|
||||||
|
// Not mutex-protected, should be called before any gRPC functions.
|
||||||
|
func SetLoggerV2(l LoggerV2) {
|
||||||
|
if _, ok := l.(*componentData); ok {
|
||||||
|
panic("cannot use component logger as grpclog logger")
|
||||||
|
}
|
||||||
|
internal.LoggerV2Impl = l
|
||||||
|
internal.DepthLoggerV2Impl, _ = l.(internal.DepthLoggerV2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLoggerV2 creates a loggerV2 with the provided writers.
|
||||||
|
// Fatal logs will be written to errorW, warningW, infoW, followed by exit(1).
|
||||||
|
// Error logs will be written to errorW, warningW and infoW.
|
||||||
|
// Warning logs will be written to warningW and infoW.
|
||||||
|
// Info logs will be written to infoW.
|
||||||
|
func NewLoggerV2(infoW, warningW, errorW io.Writer) LoggerV2 {
|
||||||
|
return internal.NewLoggerV2(infoW, warningW, errorW, internal.LoggerV2Config{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLoggerV2WithVerbosity creates a loggerV2 with the provided writers and
|
||||||
|
// verbosity level.
|
||||||
|
func NewLoggerV2WithVerbosity(infoW, warningW, errorW io.Writer, v int) LoggerV2 {
|
||||||
|
return internal.NewLoggerV2(infoW, warningW, errorW, internal.LoggerV2Config{Verbosity: v})
|
||||||
|
}
|
||||||
|
|
||||||
|
// newLoggerV2 creates a loggerV2 to be used as default logger.
|
||||||
|
// All logs are written to stderr.
|
||||||
|
func newLoggerV2() LoggerV2 {
|
||||||
|
errorW := io.Discard
|
||||||
|
warningW := io.Discard
|
||||||
|
infoW := io.Discard
|
||||||
|
|
||||||
|
logLevel := os.Getenv("GRPC_GO_LOG_SEVERITY_LEVEL")
|
||||||
|
switch logLevel {
|
||||||
|
case "", "ERROR", "error": // If env is unset, set level to ERROR.
|
||||||
|
errorW = os.Stderr
|
||||||
|
case "WARNING", "warning":
|
||||||
|
warningW = os.Stderr
|
||||||
|
case "INFO", "info":
|
||||||
|
infoW = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
var v int
|
||||||
|
vLevel := os.Getenv("GRPC_GO_LOG_VERBOSITY_LEVEL")
|
||||||
|
if vl, err := strconv.Atoi(vLevel); err == nil {
|
||||||
|
v = vl
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonFormat := strings.EqualFold(os.Getenv("GRPC_GO_LOG_FORMATTER"), "json")
|
||||||
|
|
||||||
|
return internal.NewLoggerV2(infoW, warningW, errorW, internal.LoggerV2Config{
|
||||||
|
Verbosity: v,
|
||||||
|
FormatJSON: jsonFormat,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DepthLoggerV2 logs at a specified call frame. If a LoggerV2 also implements
|
||||||
|
// DepthLoggerV2, the below functions will be called with the appropriate stack
|
||||||
|
// depth set for trivial functions the logger may ignore.
|
||||||
|
//
|
||||||
|
// # Experimental
|
||||||
|
//
|
||||||
|
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
|
||||||
|
// later release.
|
||||||
|
type DepthLoggerV2 internal.DepthLoggerV2
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2016 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 grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnaryInvoker is called by UnaryClientInterceptor to complete RPCs.
|
||||||
|
type UnaryInvoker func(ctx context.Context, method string, req, reply any, cc *ClientConn, opts ...CallOption) error
|
||||||
|
|
||||||
|
// UnaryClientInterceptor intercepts the execution of a unary RPC on the client.
|
||||||
|
// Unary interceptors can be specified as a DialOption, using
|
||||||
|
// WithUnaryInterceptor() or WithChainUnaryInterceptor(), when creating a
|
||||||
|
// ClientConn. When a unary interceptor(s) is set on a ClientConn, gRPC
|
||||||
|
// delegates all unary RPC invocations to the interceptor, and it is the
|
||||||
|
// responsibility of the interceptor to call invoker to complete the processing
|
||||||
|
// of the RPC.
|
||||||
|
//
|
||||||
|
// method is the RPC name. req and reply are the corresponding request and
|
||||||
|
// response messages. cc is the ClientConn on which the RPC was invoked. invoker
|
||||||
|
// is the handler to complete the RPC and it is the responsibility of the
|
||||||
|
// interceptor to call it. opts contain all applicable call options, including
|
||||||
|
// defaults from the ClientConn as well as per-call options.
|
||||||
|
//
|
||||||
|
// The returned error must be compatible with the status package.
|
||||||
|
type UnaryClientInterceptor func(ctx context.Context, method string, req, reply any, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error
|
||||||
|
|
||||||
|
// Streamer is called by StreamClientInterceptor to create a ClientStream.
|
||||||
|
type Streamer func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (ClientStream, error)
|
||||||
|
|
||||||
|
// StreamClientInterceptor intercepts the creation of a ClientStream. Stream
|
||||||
|
// interceptors can be specified as a DialOption, using WithStreamInterceptor()
|
||||||
|
// or WithChainStreamInterceptor(), when creating a ClientConn. When a stream
|
||||||
|
// interceptor(s) is set on the ClientConn, gRPC delegates all stream creations
|
||||||
|
// to the interceptor, and it is the responsibility of the interceptor to call
|
||||||
|
// streamer.
|
||||||
|
//
|
||||||
|
// desc contains a description of the stream. cc is the ClientConn on which the
|
||||||
|
// RPC was invoked. streamer is the handler to create a ClientStream and it is
|
||||||
|
// the responsibility of the interceptor to call it. opts contain all applicable
|
||||||
|
// call options, including defaults from the ClientConn as well as per-call
|
||||||
|
// options.
|
||||||
|
//
|
||||||
|
// StreamClientInterceptor may return a custom ClientStream to intercept all I/O
|
||||||
|
// operations. The returned error must be compatible with the status package.
|
||||||
|
type StreamClientInterceptor func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error)
|
||||||
|
|
||||||
|
// UnaryServerInfo consists of various information about a unary RPC on
|
||||||
|
// server side. All per-rpc information may be mutated by the interceptor.
|
||||||
|
type UnaryServerInfo struct {
|
||||||
|
// Server is the service implementation the user provides. This is read-only.
|
||||||
|
Server any
|
||||||
|
// FullMethod is the full RPC method string, i.e., /package.service/method.
|
||||||
|
FullMethod string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnaryHandler defines the handler invoked by UnaryServerInterceptor to complete the normal
|
||||||
|
// execution of a unary RPC.
|
||||||
|
//
|
||||||
|
// If a UnaryHandler returns an error, it should either be produced by the
|
||||||
|
// status package, or be one of the context errors. Otherwise, gRPC will use
|
||||||
|
// codes.Unknown as the status code and err.Error() as the status message of the
|
||||||
|
// RPC.
|
||||||
|
type UnaryHandler func(ctx context.Context, req any) (any, error)
|
||||||
|
|
||||||
|
// UnaryServerInterceptor provides a hook to intercept the execution of a unary RPC on the server. info
|
||||||
|
// contains all the information of this RPC the interceptor can operate on. And handler is the wrapper
|
||||||
|
// of the service method implementation. It is the responsibility of the interceptor to invoke handler
|
||||||
|
// to complete the RPC.
|
||||||
|
type UnaryServerInterceptor func(ctx context.Context, req any, info *UnaryServerInfo, handler UnaryHandler) (resp any, err error)
|
||||||
|
|
||||||
|
// StreamServerInfo consists of various information about a streaming RPC on
|
||||||
|
// server side. All per-rpc information may be mutated by the interceptor.
|
||||||
|
type StreamServerInfo struct {
|
||||||
|
// FullMethod is the full RPC method string, i.e., /package.service/method.
|
||||||
|
FullMethod string
|
||||||
|
// IsClientStream indicates whether the RPC is a client streaming RPC.
|
||||||
|
IsClientStream bool
|
||||||
|
// IsServerStream indicates whether the RPC is a server streaming RPC.
|
||||||
|
IsServerStream bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamServerInterceptor provides a hook to intercept the execution of a
|
||||||
|
// streaming RPC on the server.
|
||||||
|
//
|
||||||
|
// srv is the service implementation on which the RPC was invoked, and needs to
|
||||||
|
// be passed to handler, and not used otherwise. ss is the server side of the
|
||||||
|
// stream. info contains all the information of this RPC the interceptor can
|
||||||
|
// operate on. And handler is the service method implementation. It is the
|
||||||
|
// responsibility of the interceptor to invoke handler to complete the RPC.
|
||||||
|
type StreamServerInterceptor func(srv any, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2017 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 backoff implement the backoff strategy for gRPC.
|
||||||
|
//
|
||||||
|
// This is kept in internal until the gRPC project decides whether or not to
|
||||||
|
// allow alternative backoff strategies.
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
rand "math/rand/v2"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
grpcbackoff "google.golang.org/grpc/backoff"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Strategy defines the methodology for backing off after a grpc connection
|
||||||
|
// failure.
|
||||||
|
type Strategy interface {
|
||||||
|
// Backoff returns the amount of time to wait before the next retry given
|
||||||
|
// the number of consecutive failures.
|
||||||
|
Backoff(retries int) time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultExponential is an exponential backoff implementation using the
|
||||||
|
// default values for all the configurable knobs defined in
|
||||||
|
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
|
||||||
|
var DefaultExponential = Exponential{Config: grpcbackoff.DefaultConfig}
|
||||||
|
|
||||||
|
// Exponential implements exponential backoff algorithm as defined in
|
||||||
|
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
|
||||||
|
type Exponential struct {
|
||||||
|
// Config contains all options to configure the backoff algorithm.
|
||||||
|
Config grpcbackoff.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backoff returns the amount of time to wait before the next retry given the
|
||||||
|
// number of retries.
|
||||||
|
func (bc Exponential) Backoff(retries int) time.Duration {
|
||||||
|
if retries == 0 {
|
||||||
|
return bc.Config.BaseDelay
|
||||||
|
}
|
||||||
|
backoff, max := float64(bc.Config.BaseDelay), float64(bc.Config.MaxDelay)
|
||||||
|
for backoff < max && retries > 0 {
|
||||||
|
backoff *= bc.Config.Multiplier
|
||||||
|
retries--
|
||||||
|
}
|
||||||
|
if backoff > max {
|
||||||
|
backoff = max
|
||||||
|
}
|
||||||
|
// Randomize backoff delays so that if a cluster of requests start at
|
||||||
|
// the same time, they won't operate in lockstep.
|
||||||
|
backoff *= 1 + bc.Config.Jitter*(rand.Float64()*2-1)
|
||||||
|
if backoff < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return time.Duration(backoff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrResetBackoff is the error to be returned by the function executed by RunF,
|
||||||
|
// to instruct the latter to reset its backoff state.
|
||||||
|
var ErrResetBackoff = errors.New("reset backoff state")
|
||||||
|
|
||||||
|
// RunF provides a convenient way to run a function f repeatedly until the
|
||||||
|
// context expires or f returns a non-nil error that is not ErrResetBackoff.
|
||||||
|
// When f returns ErrResetBackoff, RunF continues to run f, but resets its
|
||||||
|
// backoff state before doing so. backoff accepts an integer representing the
|
||||||
|
// number of retries, and returns the amount of time to backoff.
|
||||||
|
func RunF(ctx context.Context, f func() error, backoff func(int) time.Duration) {
|
||||||
|
attempt := 0
|
||||||
|
timer := time.NewTimer(0)
|
||||||
|
for ctx.Err() == nil {
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
case <-ctx.Done():
|
||||||
|
timer.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := f()
|
||||||
|
if errors.Is(err, ErrResetBackoff) {
|
||||||
|
timer.Reset(0)
|
||||||
|
attempt = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timer.Reset(backoff(attempt))
|
||||||
|
attempt++
|
||||||
|
}
|
||||||
|
}
|
||||||
84
vendor/google.golang.org/grpc/internal/balancer/gracefulswitch/config.go
generated
vendored
Normal file
84
vendor/google.golang.org/grpc/internal/balancer/gracefulswitch/config.go
generated
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 gracefulswitch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/serviceconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lbConfig struct {
|
||||||
|
serviceconfig.LoadBalancingConfig
|
||||||
|
|
||||||
|
childBuilder balancer.Builder
|
||||||
|
childConfig serviceconfig.LoadBalancingConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildName returns the name of the child balancer of the gracefulswitch
|
||||||
|
// Balancer.
|
||||||
|
func ChildName(l serviceconfig.LoadBalancingConfig) string {
|
||||||
|
return l.(*lbConfig).childBuilder.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseConfig parses a child config list and returns a LB config for the
|
||||||
|
// gracefulswitch Balancer.
|
||||||
|
//
|
||||||
|
// cfg is expected to be a json.RawMessage containing a JSON array of LB policy
|
||||||
|
// names + configs as the format of the "loadBalancingConfig" field in
|
||||||
|
// ServiceConfig. It returns a type that should be passed to
|
||||||
|
// UpdateClientConnState in the BalancerConfig field.
|
||||||
|
func ParseConfig(cfg json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
||||||
|
var lbCfg []map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(cfg, &lbCfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i, e := range lbCfg {
|
||||||
|
if len(e) != 1 {
|
||||||
|
return nil, fmt.Errorf("expected a JSON struct with one entry; received entry %v at index %d", e, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var jsonCfg json.RawMessage
|
||||||
|
for name, jsonCfg = range e {
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := balancer.Get(name)
|
||||||
|
if builder == nil {
|
||||||
|
// Skip unregistered balancer names.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parser, ok := builder.(balancer.ConfigParser)
|
||||||
|
if !ok {
|
||||||
|
// This is a valid child with no config.
|
||||||
|
return &lbConfig{childBuilder: builder}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := parser.ParseConfig(jsonCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing config for policy %q: %v", name, err)
|
||||||
|
}
|
||||||
|
return &lbConfig{childBuilder: builder, childConfig: cfg}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no supported policies found in config: %v", string(cfg))
|
||||||
|
}
|
||||||
421
vendor/google.golang.org/grpc/internal/balancer/gracefulswitch/gracefulswitch.go
generated
vendored
Normal file
421
vendor/google.golang.org/grpc/internal/balancer/gracefulswitch/gracefulswitch.go
generated
vendored
Normal file
|
|
@ -0,0 +1,421 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2022 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 gracefulswitch implements a graceful switch load balancer.
|
||||||
|
package gracefulswitch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/balancer/base"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errBalancerClosed = errors.New("gracefulSwitchBalancer is closed")
|
||||||
|
var _ balancer.Balancer = (*Balancer)(nil)
|
||||||
|
|
||||||
|
// NewBalancer returns a graceful switch Balancer.
|
||||||
|
func NewBalancer(cc balancer.ClientConn, opts balancer.BuildOptions) *Balancer {
|
||||||
|
return &Balancer{
|
||||||
|
cc: cc,
|
||||||
|
bOpts: opts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Balancer is a utility to gracefully switch from one balancer to
|
||||||
|
// a new balancer. It implements the balancer.Balancer interface.
|
||||||
|
type Balancer struct {
|
||||||
|
bOpts balancer.BuildOptions
|
||||||
|
cc balancer.ClientConn
|
||||||
|
|
||||||
|
// mu protects the following fields and all fields within balancerCurrent
|
||||||
|
// and balancerPending. mu does not need to be held when calling into the
|
||||||
|
// child balancers, as all calls into these children happen only as a direct
|
||||||
|
// result of a call into the gracefulSwitchBalancer, which are also
|
||||||
|
// guaranteed to be synchronous. There is one exception: an UpdateState call
|
||||||
|
// from a child balancer when current and pending are populated can lead to
|
||||||
|
// calling Close() on the current. To prevent that racing with an
|
||||||
|
// UpdateSubConnState from the channel, we hold currentMu during Close and
|
||||||
|
// UpdateSubConnState calls.
|
||||||
|
mu sync.Mutex
|
||||||
|
balancerCurrent *balancerWrapper
|
||||||
|
balancerPending *balancerWrapper
|
||||||
|
closed bool // set to true when this balancer is closed
|
||||||
|
|
||||||
|
// currentMu must be locked before mu. This mutex guards against this
|
||||||
|
// sequence of events: UpdateSubConnState() called, finds the
|
||||||
|
// balancerCurrent, gives up lock, updateState comes in, causes Close() on
|
||||||
|
// balancerCurrent before the UpdateSubConnState is called on the
|
||||||
|
// balancerCurrent.
|
||||||
|
currentMu sync.Mutex
|
||||||
|
|
||||||
|
// activeGoroutines tracks all the goroutines that this balancer has started
|
||||||
|
// and that should be waited on when the balancer closes.
|
||||||
|
activeGoroutines sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// swap swaps out the current lb with the pending lb and updates the ClientConn.
|
||||||
|
// The caller must hold gsb.mu.
|
||||||
|
func (gsb *Balancer) swap() {
|
||||||
|
gsb.cc.UpdateState(gsb.balancerPending.lastState)
|
||||||
|
cur := gsb.balancerCurrent
|
||||||
|
gsb.balancerCurrent = gsb.balancerPending
|
||||||
|
gsb.balancerPending = nil
|
||||||
|
gsb.activeGoroutines.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer gsb.activeGoroutines.Done()
|
||||||
|
gsb.currentMu.Lock()
|
||||||
|
defer gsb.currentMu.Unlock()
|
||||||
|
cur.Close()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function that checks if the balancer passed in is current or pending.
|
||||||
|
// The caller must hold gsb.mu.
|
||||||
|
func (gsb *Balancer) balancerCurrentOrPending(bw *balancerWrapper) bool {
|
||||||
|
return bw == gsb.balancerCurrent || bw == gsb.balancerPending
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwitchTo initializes the graceful switch process, which completes based on
|
||||||
|
// connectivity state changes on the current/pending balancer. Thus, the switch
|
||||||
|
// process is not complete when this method returns. This method must be called
|
||||||
|
// synchronously alongside the rest of the balancer.Balancer methods this
|
||||||
|
// Graceful Switch Balancer implements.
|
||||||
|
//
|
||||||
|
// Deprecated: use ParseConfig and pass a parsed config to UpdateClientConnState
|
||||||
|
// to cause the Balancer to automatically change to the new child when necessary.
|
||||||
|
func (gsb *Balancer) SwitchTo(builder balancer.Builder) error {
|
||||||
|
_, err := gsb.switchTo(builder)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gsb *Balancer) switchTo(builder balancer.Builder) (*balancerWrapper, error) {
|
||||||
|
gsb.mu.Lock()
|
||||||
|
if gsb.closed {
|
||||||
|
gsb.mu.Unlock()
|
||||||
|
return nil, errBalancerClosed
|
||||||
|
}
|
||||||
|
bw := &balancerWrapper{
|
||||||
|
ClientConn: gsb.cc,
|
||||||
|
builder: builder,
|
||||||
|
gsb: gsb,
|
||||||
|
lastState: balancer.State{
|
||||||
|
ConnectivityState: connectivity.Connecting,
|
||||||
|
Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable),
|
||||||
|
},
|
||||||
|
subconns: make(map[balancer.SubConn]bool),
|
||||||
|
}
|
||||||
|
balToClose := gsb.balancerPending // nil if there is no pending balancer
|
||||||
|
if gsb.balancerCurrent == nil {
|
||||||
|
gsb.balancerCurrent = bw
|
||||||
|
} else {
|
||||||
|
gsb.balancerPending = bw
|
||||||
|
}
|
||||||
|
gsb.mu.Unlock()
|
||||||
|
balToClose.Close()
|
||||||
|
// This function takes a builder instead of a balancer because builder.Build
|
||||||
|
// can call back inline, and this utility needs to handle the callbacks.
|
||||||
|
newBalancer := builder.Build(bw, gsb.bOpts)
|
||||||
|
if newBalancer == nil {
|
||||||
|
// This is illegal and should never happen; we clear the balancerWrapper
|
||||||
|
// we were constructing if it happens to avoid a potential panic.
|
||||||
|
gsb.mu.Lock()
|
||||||
|
if gsb.balancerPending != nil {
|
||||||
|
gsb.balancerPending = nil
|
||||||
|
} else {
|
||||||
|
gsb.balancerCurrent = nil
|
||||||
|
}
|
||||||
|
gsb.mu.Unlock()
|
||||||
|
return nil, balancer.ErrBadResolverState
|
||||||
|
}
|
||||||
|
|
||||||
|
// This write doesn't need to take gsb.mu because this field never gets read
|
||||||
|
// or written to on any calls from the current or pending. Calls from grpc
|
||||||
|
// to this balancer are guaranteed to be called synchronously, so this
|
||||||
|
// bw.Balancer field will never be forwarded to until this SwitchTo()
|
||||||
|
// function returns.
|
||||||
|
bw.Balancer = newBalancer
|
||||||
|
return bw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns nil if the graceful switch balancer is closed.
|
||||||
|
func (gsb *Balancer) latestBalancer() *balancerWrapper {
|
||||||
|
gsb.mu.Lock()
|
||||||
|
defer gsb.mu.Unlock()
|
||||||
|
if gsb.balancerPending != nil {
|
||||||
|
return gsb.balancerPending
|
||||||
|
}
|
||||||
|
return gsb.balancerCurrent
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateClientConnState forwards the update to the latest balancer created.
|
||||||
|
//
|
||||||
|
// If the state's BalancerConfig is the config returned by a call to
|
||||||
|
// gracefulswitch.ParseConfig, then this function will automatically SwitchTo
|
||||||
|
// the balancer indicated by the config before forwarding its config to it, if
|
||||||
|
// necessary.
|
||||||
|
func (gsb *Balancer) UpdateClientConnState(state balancer.ClientConnState) error {
|
||||||
|
// The resolver data is only relevant to the most recent LB Policy.
|
||||||
|
balToUpdate := gsb.latestBalancer()
|
||||||
|
gsbCfg, ok := state.BalancerConfig.(*lbConfig)
|
||||||
|
if ok {
|
||||||
|
// Switch to the child in the config unless it is already active.
|
||||||
|
if balToUpdate == nil || gsbCfg.childBuilder.Name() != balToUpdate.builder.Name() {
|
||||||
|
var err error
|
||||||
|
balToUpdate, err = gsb.switchTo(gsbCfg.childBuilder)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not switch to new child balancer: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Unwrap the child balancer's config.
|
||||||
|
state.BalancerConfig = gsbCfg.childConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
if balToUpdate == nil {
|
||||||
|
return errBalancerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform this call without gsb.mu to prevent deadlocks if the child calls
|
||||||
|
// back into the channel. The latest balancer can never be closed during a
|
||||||
|
// call from the channel, even without gsb.mu held.
|
||||||
|
return balToUpdate.UpdateClientConnState(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolverError forwards the error to the latest balancer created.
|
||||||
|
func (gsb *Balancer) ResolverError(err error) {
|
||||||
|
// The resolver data is only relevant to the most recent LB Policy.
|
||||||
|
balToUpdate := gsb.latestBalancer()
|
||||||
|
if balToUpdate == nil {
|
||||||
|
gsb.cc.UpdateState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.TransientFailure,
|
||||||
|
Picker: base.NewErrPicker(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Perform this call without gsb.mu to prevent deadlocks if the child calls
|
||||||
|
// back into the channel. The latest balancer can never be closed during a
|
||||||
|
// call from the channel, even without gsb.mu held.
|
||||||
|
balToUpdate.ResolverError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitIdle forwards the call to the latest balancer created.
|
||||||
|
//
|
||||||
|
// If the latest balancer does not support ExitIdle, the subConns are
|
||||||
|
// re-connected to manually.
|
||||||
|
func (gsb *Balancer) ExitIdle() {
|
||||||
|
balToUpdate := gsb.latestBalancer()
|
||||||
|
if balToUpdate == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// There is no need to protect this read with a mutex, as the write to the
|
||||||
|
// Balancer field happens in SwitchTo, which completes before this can be
|
||||||
|
// called.
|
||||||
|
balToUpdate.ExitIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateSubConnState forwards the update to the appropriate child.
|
||||||
|
func (gsb *Balancer) updateSubConnState(sc balancer.SubConn, state balancer.SubConnState, cb func(balancer.SubConnState)) {
|
||||||
|
gsb.currentMu.Lock()
|
||||||
|
defer gsb.currentMu.Unlock()
|
||||||
|
gsb.mu.Lock()
|
||||||
|
// Forward update to the appropriate child. Even if there is a pending
|
||||||
|
// balancer, the current balancer should continue to get SubConn updates to
|
||||||
|
// maintain the proper state while the pending is still connecting.
|
||||||
|
var balToUpdate *balancerWrapper
|
||||||
|
if gsb.balancerCurrent != nil && gsb.balancerCurrent.subconns[sc] {
|
||||||
|
balToUpdate = gsb.balancerCurrent
|
||||||
|
} else if gsb.balancerPending != nil && gsb.balancerPending.subconns[sc] {
|
||||||
|
balToUpdate = gsb.balancerPending
|
||||||
|
}
|
||||||
|
if balToUpdate == nil {
|
||||||
|
// SubConn belonged to a stale lb policy that has not yet fully closed,
|
||||||
|
// or the balancer was already closed.
|
||||||
|
gsb.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if state.ConnectivityState == connectivity.Shutdown {
|
||||||
|
delete(balToUpdate.subconns, sc)
|
||||||
|
}
|
||||||
|
gsb.mu.Unlock()
|
||||||
|
if cb != nil {
|
||||||
|
cb(state)
|
||||||
|
} else {
|
||||||
|
balToUpdate.UpdateSubConnState(sc, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSubConnState forwards the update to the appropriate child.
|
||||||
|
func (gsb *Balancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
|
||||||
|
gsb.updateSubConnState(sc, state, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes any active child balancers.
|
||||||
|
func (gsb *Balancer) Close() {
|
||||||
|
gsb.mu.Lock()
|
||||||
|
gsb.closed = true
|
||||||
|
currentBalancerToClose := gsb.balancerCurrent
|
||||||
|
gsb.balancerCurrent = nil
|
||||||
|
pendingBalancerToClose := gsb.balancerPending
|
||||||
|
gsb.balancerPending = nil
|
||||||
|
gsb.mu.Unlock()
|
||||||
|
|
||||||
|
currentBalancerToClose.Close()
|
||||||
|
pendingBalancerToClose.Close()
|
||||||
|
gsb.activeGoroutines.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// balancerWrapper wraps a balancer.Balancer, and overrides some Balancer
|
||||||
|
// methods to help cleanup SubConns created by the wrapped balancer.
|
||||||
|
//
|
||||||
|
// It implements the balancer.ClientConn interface and is passed down in that
|
||||||
|
// capacity to the wrapped balancer. It maintains a set of subConns created by
|
||||||
|
// the wrapped balancer and calls from the latter to create/update/shutdown
|
||||||
|
// SubConns update this set before being forwarded to the parent ClientConn.
|
||||||
|
// State updates from the wrapped balancer can result in invocation of the
|
||||||
|
// graceful switch logic.
|
||||||
|
type balancerWrapper struct {
|
||||||
|
balancer.ClientConn
|
||||||
|
balancer.Balancer
|
||||||
|
gsb *Balancer
|
||||||
|
builder balancer.Builder
|
||||||
|
|
||||||
|
lastState balancer.State
|
||||||
|
subconns map[balancer.SubConn]bool // subconns created by this balancer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the underlying LB policy and shuts down the subconns it
|
||||||
|
// created. bw must not be referenced via balancerCurrent or balancerPending in
|
||||||
|
// gsb when called. gsb.mu must not be held. Does not panic with a nil
|
||||||
|
// receiver.
|
||||||
|
func (bw *balancerWrapper) Close() {
|
||||||
|
// before Close is called.
|
||||||
|
if bw == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// There is no need to protect this read with a mutex, as Close() is
|
||||||
|
// impossible to be called concurrently with the write in SwitchTo(). The
|
||||||
|
// callsites of Close() for this balancer in Graceful Switch Balancer will
|
||||||
|
// never be called until SwitchTo() returns.
|
||||||
|
bw.Balancer.Close()
|
||||||
|
bw.gsb.mu.Lock()
|
||||||
|
for sc := range bw.subconns {
|
||||||
|
sc.Shutdown()
|
||||||
|
}
|
||||||
|
bw.gsb.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *balancerWrapper) UpdateState(state balancer.State) {
|
||||||
|
// Hold the mutex for this entire call to ensure it cannot occur
|
||||||
|
// concurrently with other updateState() calls. This causes updates to
|
||||||
|
// lastState and calls to cc.UpdateState to happen atomically.
|
||||||
|
bw.gsb.mu.Lock()
|
||||||
|
defer bw.gsb.mu.Unlock()
|
||||||
|
bw.lastState = state
|
||||||
|
|
||||||
|
// If Close() acquires the mutex before UpdateState(), the balancer
|
||||||
|
// will already have been removed from the current or pending state when
|
||||||
|
// reaching this point.
|
||||||
|
if !bw.gsb.balancerCurrentOrPending(bw) {
|
||||||
|
// Returning here ensures that (*Balancer).swap() is not invoked after
|
||||||
|
// (*Balancer).Close() and therefore prevents "use after close".
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if bw == bw.gsb.balancerCurrent {
|
||||||
|
// In the case that the current balancer exits READY, and there is a pending
|
||||||
|
// balancer, you can forward the pending balancer's cached State up to
|
||||||
|
// ClientConn and swap the pending into the current. This is because there
|
||||||
|
// is no reason to gracefully switch from and keep using the old policy as
|
||||||
|
// the ClientConn is not connected to any backends.
|
||||||
|
if state.ConnectivityState != connectivity.Ready && bw.gsb.balancerPending != nil {
|
||||||
|
bw.gsb.swap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Even if there is a pending balancer waiting to be gracefully switched to,
|
||||||
|
// continue to forward current balancer updates to the Client Conn. Ignoring
|
||||||
|
// state + picker from the current would cause undefined behavior/cause the
|
||||||
|
// system to behave incorrectly from the current LB policies perspective.
|
||||||
|
// Also, the current LB is still being used by grpc to choose SubConns per
|
||||||
|
// RPC, and thus should use the most updated form of the current balancer.
|
||||||
|
bw.gsb.cc.UpdateState(state)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// This method is now dealing with a state update from the pending balancer.
|
||||||
|
// If the current balancer is currently in a state other than READY, the new
|
||||||
|
// policy can be swapped into place immediately. This is because there is no
|
||||||
|
// reason to gracefully switch from and keep using the old policy as the
|
||||||
|
// ClientConn is not connected to any backends.
|
||||||
|
if state.ConnectivityState != connectivity.Connecting || bw.gsb.balancerCurrent.lastState.ConnectivityState != connectivity.Ready {
|
||||||
|
bw.gsb.swap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *balancerWrapper) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) {
|
||||||
|
bw.gsb.mu.Lock()
|
||||||
|
if !bw.gsb.balancerCurrentOrPending(bw) {
|
||||||
|
bw.gsb.mu.Unlock()
|
||||||
|
return nil, fmt.Errorf("%T at address %p that called NewSubConn is deleted", bw, bw)
|
||||||
|
}
|
||||||
|
bw.gsb.mu.Unlock()
|
||||||
|
|
||||||
|
var sc balancer.SubConn
|
||||||
|
oldListener := opts.StateListener
|
||||||
|
opts.StateListener = func(state balancer.SubConnState) { bw.gsb.updateSubConnState(sc, state, oldListener) }
|
||||||
|
sc, err := bw.gsb.cc.NewSubConn(addrs, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bw.gsb.mu.Lock()
|
||||||
|
if !bw.gsb.balancerCurrentOrPending(bw) { // balancer was closed during this call
|
||||||
|
sc.Shutdown()
|
||||||
|
bw.gsb.mu.Unlock()
|
||||||
|
return nil, fmt.Errorf("%T at address %p that called NewSubConn is deleted", bw, bw)
|
||||||
|
}
|
||||||
|
bw.subconns[sc] = true
|
||||||
|
bw.gsb.mu.Unlock()
|
||||||
|
return sc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *balancerWrapper) ResolveNow(opts resolver.ResolveNowOptions) {
|
||||||
|
// Ignore ResolveNow requests from anything other than the most recent
|
||||||
|
// balancer, because older balancers were already removed from the config.
|
||||||
|
if bw != bw.gsb.latestBalancer() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bw.gsb.cc.ResolveNow(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *balancerWrapper) RemoveSubConn(sc balancer.SubConn) {
|
||||||
|
// Note: existing third party balancers may call this, so it must remain
|
||||||
|
// until RemoveSubConn is fully removed.
|
||||||
|
sc.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *balancerWrapper) UpdateAddresses(sc balancer.SubConn, addrs []resolver.Address) {
|
||||||
|
bw.gsb.mu.Lock()
|
||||||
|
if !bw.gsb.balancerCurrentOrPending(bw) {
|
||||||
|
bw.gsb.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bw.gsb.mu.Unlock()
|
||||||
|
bw.gsb.cc.UpdateAddresses(sc, addrs)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2025 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 weight contains utilities to manage endpoint weights. Weights are
|
||||||
|
// used by LB policies such as ringhash to distribute load across multiple
|
||||||
|
// endpoints.
|
||||||
|
package weight
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// attributeKey is the type used as the key to store EndpointInfo in the
|
||||||
|
// Attributes field of resolver.Endpoint.
|
||||||
|
type attributeKey struct{}
|
||||||
|
|
||||||
|
// EndpointInfo will be stored in the Attributes field of Endpoints in order to
|
||||||
|
// use the ringhash balancer.
|
||||||
|
type EndpointInfo struct {
|
||||||
|
Weight uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal allows the values to be compared by Attributes.Equal.
|
||||||
|
func (a EndpointInfo) Equal(o any) bool {
|
||||||
|
oa, ok := o.(EndpointInfo)
|
||||||
|
return ok && oa.Weight == a.Weight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set returns a copy of endpoint in which the Attributes field is updated with
|
||||||
|
// EndpointInfo.
|
||||||
|
func Set(endpoint resolver.Endpoint, epInfo EndpointInfo) resolver.Endpoint {
|
||||||
|
endpoint.Attributes = endpoint.Attributes.WithValue(attributeKey{}, epInfo)
|
||||||
|
return endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a human-readable representation of EndpointInfo.
|
||||||
|
// This method is intended for logging, testing, and debugging purposes only.
|
||||||
|
// Do not rely on the output format, as it is not guaranteed to remain stable.
|
||||||
|
func (a EndpointInfo) String() string {
|
||||||
|
return fmt.Sprintf("Weight: %d", a.Weight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromEndpoint returns the EndpointInfo stored in the Attributes field of an
|
||||||
|
// endpoint. It returns an empty EndpointInfo if attribute is not found.
|
||||||
|
func FromEndpoint(endpoint resolver.Endpoint) EndpointInfo {
|
||||||
|
v := endpoint.Attributes.Value(attributeKey{})
|
||||||
|
ei, _ := v.(EndpointInfo)
|
||||||
|
return ei
|
||||||
|
}
|
||||||
|
|
@ -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) any
|
||||||
|
}
|
||||||
|
|
||||||
|
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) any {
|
||||||
|
if parser == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return parser.Parse(md)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2018 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 binarylog implementation binary logging as defined in
|
||||||
|
// https://github.com/grpc/proposal/blob/master/A16-binary-logging.md.
|
||||||
|
package binarylog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
"google.golang.org/grpc/internal/grpcutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var grpclogLogger = grpclog.Component("binarylog")
|
||||||
|
|
||||||
|
// Logger specifies MethodLoggers for method names with a Log call that
|
||||||
|
// takes a context.
|
||||||
|
//
|
||||||
|
// This is used in the 1.0 release of gcp/observability, and thus must not be
|
||||||
|
// deleted or changed.
|
||||||
|
type Logger interface {
|
||||||
|
GetMethodLogger(methodName string) MethodLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// binLogger is the global binary logger for the binary. One of this should be
|
||||||
|
// built at init time from the configuration (environment variable or flags).
|
||||||
|
//
|
||||||
|
// It is used to get a MethodLogger for each individual method.
|
||||||
|
var binLogger Logger
|
||||||
|
|
||||||
|
// SetLogger sets the binary logger.
|
||||||
|
//
|
||||||
|
// Only call this at init time.
|
||||||
|
func SetLogger(l Logger) {
|
||||||
|
binLogger = l
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogger gets the binary logger.
|
||||||
|
//
|
||||||
|
// Only call this at init time.
|
||||||
|
func GetLogger() Logger {
|
||||||
|
return binLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMethodLogger returns the MethodLogger for the given methodName.
|
||||||
|
//
|
||||||
|
// methodName should be in the format of "/service/method".
|
||||||
|
//
|
||||||
|
// Each MethodLogger returned by this method is a new instance. This is to
|
||||||
|
// generate sequence id within the call.
|
||||||
|
func GetMethodLogger(methodName string) MethodLogger {
|
||||||
|
if binLogger == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return binLogger.GetMethodLogger(methodName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
const envStr = "GRPC_BINARY_LOG_FILTER"
|
||||||
|
configStr := os.Getenv(envStr)
|
||||||
|
binLogger = NewLoggerFromConfigString(configStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodLoggerConfig contains the setting for logging behavior of a method
|
||||||
|
// logger. Currently, it contains the max length of header and message.
|
||||||
|
type MethodLoggerConfig struct {
|
||||||
|
// Max length of header and message.
|
||||||
|
Header, Message uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerConfig contains the config for loggers to create method loggers.
|
||||||
|
type LoggerConfig struct {
|
||||||
|
All *MethodLoggerConfig
|
||||||
|
Services map[string]*MethodLoggerConfig
|
||||||
|
Methods map[string]*MethodLoggerConfig
|
||||||
|
|
||||||
|
Blacklist map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type logger struct {
|
||||||
|
config LoggerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLoggerFromConfig builds a logger with the given LoggerConfig.
|
||||||
|
func NewLoggerFromConfig(config LoggerConfig) Logger {
|
||||||
|
return &logger{config: config}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEmptyLogger creates an empty logger. The map fields need to be filled in
|
||||||
|
// using the set* functions.
|
||||||
|
func newEmptyLogger() *logger {
|
||||||
|
return &logger{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set method logger for "*".
|
||||||
|
func (l *logger) setDefaultMethodLogger(ml *MethodLoggerConfig) error {
|
||||||
|
if l.config.All != nil {
|
||||||
|
return fmt.Errorf("conflicting global rules found")
|
||||||
|
}
|
||||||
|
l.config.All = ml
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set method logger for "service/*".
|
||||||
|
//
|
||||||
|
// New MethodLogger with same service overrides the old one.
|
||||||
|
func (l *logger) setServiceMethodLogger(service string, ml *MethodLoggerConfig) error {
|
||||||
|
if _, ok := l.config.Services[service]; ok {
|
||||||
|
return fmt.Errorf("conflicting service rules for service %v found", service)
|
||||||
|
}
|
||||||
|
if l.config.Services == nil {
|
||||||
|
l.config.Services = make(map[string]*MethodLoggerConfig)
|
||||||
|
}
|
||||||
|
l.config.Services[service] = ml
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set method logger for "service/method".
|
||||||
|
//
|
||||||
|
// New MethodLogger with same method overrides the old one.
|
||||||
|
func (l *logger) setMethodMethodLogger(method string, ml *MethodLoggerConfig) error {
|
||||||
|
if _, ok := l.config.Blacklist[method]; ok {
|
||||||
|
return fmt.Errorf("conflicting blacklist rules for method %v found", method)
|
||||||
|
}
|
||||||
|
if _, ok := l.config.Methods[method]; ok {
|
||||||
|
return fmt.Errorf("conflicting method rules for method %v found", method)
|
||||||
|
}
|
||||||
|
if l.config.Methods == nil {
|
||||||
|
l.config.Methods = make(map[string]*MethodLoggerConfig)
|
||||||
|
}
|
||||||
|
l.config.Methods[method] = ml
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set blacklist method for "-service/method".
|
||||||
|
func (l *logger) setBlacklist(method string) error {
|
||||||
|
if _, ok := l.config.Blacklist[method]; ok {
|
||||||
|
return fmt.Errorf("conflicting blacklist rules for method %v found", method)
|
||||||
|
}
|
||||||
|
if _, ok := l.config.Methods[method]; ok {
|
||||||
|
return fmt.Errorf("conflicting method rules for method %v found", method)
|
||||||
|
}
|
||||||
|
if l.config.Blacklist == nil {
|
||||||
|
l.config.Blacklist = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
l.config.Blacklist[method] = struct{}{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMethodLogger returns the MethodLogger for the given methodName.
|
||||||
|
//
|
||||||
|
// methodName should be in the format of "/service/method".
|
||||||
|
//
|
||||||
|
// Each MethodLogger returned by this method is a new instance. This is to
|
||||||
|
// generate sequence id within the call.
|
||||||
|
func (l *logger) GetMethodLogger(methodName string) MethodLogger {
|
||||||
|
s, m, err := grpcutil.ParseMethod(methodName)
|
||||||
|
if err != nil {
|
||||||
|
grpclogLogger.Infof("binarylogging: failed to parse %q: %v", methodName, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ml, ok := l.config.Methods[s+"/"+m]; ok {
|
||||||
|
return NewTruncatingMethodLogger(ml.Header, ml.Message)
|
||||||
|
}
|
||||||
|
if _, ok := l.config.Blacklist[s+"/"+m]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ml, ok := l.config.Services[s]; ok {
|
||||||
|
return NewTruncatingMethodLogger(ml.Header, ml.Message)
|
||||||
|
}
|
||||||
|
if l.config.All == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return NewTruncatingMethodLogger(l.config.All.Header, l.config.All.Message)
|
||||||
|
}
|
||||||
42
vendor/google.golang.org/grpc/internal/binarylog/binarylog_testutil.go
generated
vendored
Normal file
42
vendor/google.golang.org/grpc/internal/binarylog/binarylog_testutil.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file contains exported variables/functions that are exported for testing
|
||||||
|
// only.
|
||||||
|
//
|
||||||
|
// An ideal way for this would be to put those in a *_test.go but in binarylog
|
||||||
|
// package. But this doesn't work with staticcheck with go module. Error was:
|
||||||
|
// "MdToMetadataProto not declared by package binarylog". This could be caused
|
||||||
|
// by the way staticcheck looks for files for a certain package, which doesn't
|
||||||
|
// support *_test.go files.
|
||||||
|
//
|
||||||
|
// Move those to binary_test.go when staticcheck is fixed.
|
||||||
|
|
||||||
|
package binarylog
|
||||||
|
|
||||||
|
var (
|
||||||
|
// AllLogger is a logger that logs all headers/messages for all RPCs. It's
|
||||||
|
// for testing only.
|
||||||
|
AllLogger = NewLoggerFromConfigString("*")
|
||||||
|
// MdToMetadataProto converts metadata to a binary logging proto message.
|
||||||
|
// It's for testing only.
|
||||||
|
MdToMetadataProto = mdToMetadataProto
|
||||||
|
// AddrToProto converts an address to a binary logging proto message. It's
|
||||||
|
// for testing only.
|
||||||
|
AddrToProto = addrToProto
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2018 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 binarylog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLoggerFromConfigString reads the string and build a logger. It can be used
|
||||||
|
// to build a new logger and assign it to binarylog.Logger.
|
||||||
|
//
|
||||||
|
// Example filter config strings:
|
||||||
|
// - "" Nothing will be logged
|
||||||
|
// - "*" All headers and messages will be fully logged.
|
||||||
|
// - "*{h}" Only headers will be logged.
|
||||||
|
// - "*{m:256}" Only the first 256 bytes of each message will be logged.
|
||||||
|
// - "Foo/*" Logs every method in service Foo
|
||||||
|
// - "Foo/*,-Foo/Bar" Logs every method in service Foo except method /Foo/Bar
|
||||||
|
// - "Foo/*,Foo/Bar{m:256}" Logs the first 256 bytes of each message in method
|
||||||
|
// /Foo/Bar, logs all headers and messages in every other method in service
|
||||||
|
// Foo.
|
||||||
|
//
|
||||||
|
// If two configs exist for one certain method or service, the one specified
|
||||||
|
// later overrides the previous config.
|
||||||
|
func NewLoggerFromConfigString(s string) Logger {
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
l := newEmptyLogger()
|
||||||
|
methods := strings.Split(s, ",")
|
||||||
|
for _, method := range methods {
|
||||||
|
if err := l.fillMethodLoggerWithConfigString(method); err != nil {
|
||||||
|
grpclogLogger.Warningf("failed to parse binary log config: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// fillMethodLoggerWithConfigString parses config, creates TruncatingMethodLogger and adds
|
||||||
|
// it to the right map in the logger.
|
||||||
|
func (l *logger) fillMethodLoggerWithConfigString(config string) error {
|
||||||
|
// "" is invalid.
|
||||||
|
if config == "" {
|
||||||
|
return errors.New("empty string is not a valid method binary logging config")
|
||||||
|
}
|
||||||
|
|
||||||
|
// "-service/method", blacklist, no * or {} allowed.
|
||||||
|
if config[0] == '-' {
|
||||||
|
s, m, suffix, err := parseMethodConfigAndSuffix(config[1:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid config: %q, %v", config, err)
|
||||||
|
}
|
||||||
|
if m == "*" {
|
||||||
|
return fmt.Errorf("invalid config: %q, %v", config, "* not allowed in blacklist config")
|
||||||
|
}
|
||||||
|
if suffix != "" {
|
||||||
|
return fmt.Errorf("invalid config: %q, %v", config, "header/message limit not allowed in blacklist config")
|
||||||
|
}
|
||||||
|
if err := l.setBlacklist(s + "/" + m); err != nil {
|
||||||
|
return fmt.Errorf("invalid config: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// "*{h:256;m:256}"
|
||||||
|
if config[0] == '*' {
|
||||||
|
hdr, msg, err := parseHeaderMessageLengthConfig(config[1:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid config: %q, %v", config, err)
|
||||||
|
}
|
||||||
|
if err := l.setDefaultMethodLogger(&MethodLoggerConfig{Header: hdr, Message: msg}); err != nil {
|
||||||
|
return fmt.Errorf("invalid config: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s, m, suffix, err := parseMethodConfigAndSuffix(config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid config: %q, %v", config, err)
|
||||||
|
}
|
||||||
|
hdr, msg, err := parseHeaderMessageLengthConfig(suffix)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid header/message length config: %q, %v", suffix, err)
|
||||||
|
}
|
||||||
|
if m == "*" {
|
||||||
|
if err := l.setServiceMethodLogger(s, &MethodLoggerConfig{Header: hdr, Message: msg}); err != nil {
|
||||||
|
return fmt.Errorf("invalid config: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := l.setMethodMethodLogger(s+"/"+m, &MethodLoggerConfig{Header: hdr, Message: msg}); err != nil {
|
||||||
|
return fmt.Errorf("invalid config: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TODO: this const is only used by env_config now. But could be useful for
|
||||||
|
// other config. Move to binarylog.go if necessary.
|
||||||
|
maxUInt = ^uint64(0)
|
||||||
|
|
||||||
|
// For "p.s/m" plus any suffix. Suffix will be parsed again. See test for
|
||||||
|
// expected output.
|
||||||
|
longMethodConfigRegexpStr = `^([\w./]+)/((?:\w+)|[*])(.+)?$`
|
||||||
|
|
||||||
|
// For suffix from above, "{h:123,m:123}". See test for expected output.
|
||||||
|
optionalLengthRegexpStr = `(?::(\d+))?` // Optional ":123".
|
||||||
|
headerConfigRegexpStr = `^{h` + optionalLengthRegexpStr + `}$`
|
||||||
|
messageConfigRegexpStr = `^{m` + optionalLengthRegexpStr + `}$`
|
||||||
|
headerMessageConfigRegexpStr = `^{h` + optionalLengthRegexpStr + `;m` + optionalLengthRegexpStr + `}$`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
longMethodConfigRegexp = regexp.MustCompile(longMethodConfigRegexpStr)
|
||||||
|
headerConfigRegexp = regexp.MustCompile(headerConfigRegexpStr)
|
||||||
|
messageConfigRegexp = regexp.MustCompile(messageConfigRegexpStr)
|
||||||
|
headerMessageConfigRegexp = regexp.MustCompile(headerMessageConfigRegexpStr)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Turn "service/method{h;m}" into "service", "method", "{h;m}".
|
||||||
|
func parseMethodConfigAndSuffix(c string) (service, method, suffix string, _ error) {
|
||||||
|
// Regexp result:
|
||||||
|
//
|
||||||
|
// in: "p.s/m{h:123,m:123}",
|
||||||
|
// out: []string{"p.s/m{h:123,m:123}", "p.s", "m", "{h:123,m:123}"},
|
||||||
|
match := longMethodConfigRegexp.FindStringSubmatch(c)
|
||||||
|
if match == nil {
|
||||||
|
return "", "", "", fmt.Errorf("%q contains invalid substring", c)
|
||||||
|
}
|
||||||
|
service = match[1]
|
||||||
|
method = match[2]
|
||||||
|
suffix = match[3]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn "{h:123;m:345}" into 123, 345.
|
||||||
|
//
|
||||||
|
// Return maxUInt if length is unspecified.
|
||||||
|
func parseHeaderMessageLengthConfig(c string) (hdrLenStr, msgLenStr uint64, err error) {
|
||||||
|
if c == "" {
|
||||||
|
return maxUInt, maxUInt, nil
|
||||||
|
}
|
||||||
|
// Header config only.
|
||||||
|
if match := headerConfigRegexp.FindStringSubmatch(c); match != nil {
|
||||||
|
if s := match[1]; s != "" {
|
||||||
|
hdrLenStr, err = strconv.ParseUint(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to convert %q to uint", s)
|
||||||
|
}
|
||||||
|
return hdrLenStr, 0, nil
|
||||||
|
}
|
||||||
|
return maxUInt, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message config only.
|
||||||
|
if match := messageConfigRegexp.FindStringSubmatch(c); match != nil {
|
||||||
|
if s := match[1]; s != "" {
|
||||||
|
msgLenStr, err = strconv.ParseUint(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to convert %q to uint", s)
|
||||||
|
}
|
||||||
|
return 0, msgLenStr, nil
|
||||||
|
}
|
||||||
|
return 0, maxUInt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header and message config both.
|
||||||
|
if match := headerMessageConfigRegexp.FindStringSubmatch(c); match != nil {
|
||||||
|
// Both hdr and msg are specified, but one or two of them might be empty.
|
||||||
|
hdrLenStr = maxUInt
|
||||||
|
msgLenStr = maxUInt
|
||||||
|
if s := match[1]; s != "" {
|
||||||
|
hdrLenStr, err = strconv.ParseUint(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to convert %q to uint", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s := match[2]; s != "" {
|
||||||
|
msgLenStr, err = strconv.ParseUint(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to convert %q to uint", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hdrLenStr, msgLenStr, nil
|
||||||
|
}
|
||||||
|
return 0, 0, fmt.Errorf("%q contains invalid substring", c)
|
||||||
|
}
|
||||||
446
vendor/google.golang.org/grpc/internal/binarylog/method_logger.go
generated
vendored
Normal file
446
vendor/google.golang.org/grpc/internal/binarylog/method_logger.go
generated
vendored
Normal file
|
|
@ -0,0 +1,446 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2018 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 binarylog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
binlogpb "google.golang.org/grpc/binarylog/grpc_binarylog_v1"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type callIDGenerator struct {
|
||||||
|
id uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *callIDGenerator) next() uint64 {
|
||||||
|
id := atomic.AddUint64(&g.id, 1)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset is for testing only, and doesn't need to be thread safe.
|
||||||
|
func (g *callIDGenerator) reset() {
|
||||||
|
g.id = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var idGen callIDGenerator
|
||||||
|
|
||||||
|
// MethodLogger is the sub-logger for each method.
|
||||||
|
//
|
||||||
|
// This is used in the 1.0 release of gcp/observability, and thus must not be
|
||||||
|
// deleted or changed.
|
||||||
|
type MethodLogger interface {
|
||||||
|
Log(context.Context, LogEntryConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TruncatingMethodLogger is a method logger that truncates headers and messages
|
||||||
|
// based on configured fields.
|
||||||
|
type TruncatingMethodLogger struct {
|
||||||
|
headerMaxLen, messageMaxLen uint64
|
||||||
|
|
||||||
|
callID uint64
|
||||||
|
idWithinCallGen *callIDGenerator
|
||||||
|
|
||||||
|
sink Sink // TODO(blog): make this pluggable.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTruncatingMethodLogger returns a new truncating method logger.
|
||||||
|
//
|
||||||
|
// This is used in the 1.0 release of gcp/observability, and thus must not be
|
||||||
|
// deleted or changed.
|
||||||
|
func NewTruncatingMethodLogger(h, m uint64) *TruncatingMethodLogger {
|
||||||
|
return &TruncatingMethodLogger{
|
||||||
|
headerMaxLen: h,
|
||||||
|
messageMaxLen: m,
|
||||||
|
|
||||||
|
callID: idGen.next(),
|
||||||
|
idWithinCallGen: &callIDGenerator{},
|
||||||
|
|
||||||
|
sink: DefaultSink, // TODO(blog): make it pluggable.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build is an internal only method for building the proto message out of the
|
||||||
|
// input event. It's made public to enable other library to reuse as much logic
|
||||||
|
// in TruncatingMethodLogger as possible.
|
||||||
|
func (ml *TruncatingMethodLogger) Build(c LogEntryConfig) *binlogpb.GrpcLogEntry {
|
||||||
|
m := c.toProto()
|
||||||
|
timestamp := timestamppb.Now()
|
||||||
|
m.Timestamp = timestamp
|
||||||
|
m.CallId = ml.callID
|
||||||
|
m.SequenceIdWithinCall = ml.idWithinCallGen.next()
|
||||||
|
|
||||||
|
switch pay := m.Payload.(type) {
|
||||||
|
case *binlogpb.GrpcLogEntry_ClientHeader:
|
||||||
|
m.PayloadTruncated = ml.truncateMetadata(pay.ClientHeader.GetMetadata())
|
||||||
|
case *binlogpb.GrpcLogEntry_ServerHeader:
|
||||||
|
m.PayloadTruncated = ml.truncateMetadata(pay.ServerHeader.GetMetadata())
|
||||||
|
case *binlogpb.GrpcLogEntry_Message:
|
||||||
|
m.PayloadTruncated = ml.truncateMessage(pay.Message)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log creates a proto binary log entry, and logs it to the sink.
|
||||||
|
func (ml *TruncatingMethodLogger) Log(_ context.Context, c LogEntryConfig) {
|
||||||
|
ml.sink.Write(ml.Build(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml *TruncatingMethodLogger) truncateMetadata(mdPb *binlogpb.Metadata) (truncated bool) {
|
||||||
|
if ml.headerMaxLen == maxUInt {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
bytesLimit = ml.headerMaxLen
|
||||||
|
index int
|
||||||
|
)
|
||||||
|
// At the end of the loop, index will be the first entry where the total
|
||||||
|
// size is greater than the limit:
|
||||||
|
//
|
||||||
|
// len(entry[:index]) <= ml.hdr && len(entry[:index+1]) > ml.hdr.
|
||||||
|
for ; index < len(mdPb.Entry); index++ {
|
||||||
|
entry := mdPb.Entry[index]
|
||||||
|
if entry.Key == "grpc-trace-bin" {
|
||||||
|
// "grpc-trace-bin" is a special key. It's kept in the log entry,
|
||||||
|
// but not counted towards the size limit.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
currentEntryLen := uint64(len(entry.GetKey())) + uint64(len(entry.GetValue()))
|
||||||
|
if currentEntryLen > bytesLimit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
bytesLimit -= currentEntryLen
|
||||||
|
}
|
||||||
|
truncated = index < len(mdPb.Entry)
|
||||||
|
mdPb.Entry = mdPb.Entry[:index]
|
||||||
|
return truncated
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml *TruncatingMethodLogger) truncateMessage(msgPb *binlogpb.Message) (truncated bool) {
|
||||||
|
if ml.messageMaxLen == maxUInt {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ml.messageMaxLen >= uint64(len(msgPb.Data)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
msgPb.Data = msgPb.Data[:ml.messageMaxLen]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogEntryConfig represents the configuration for binary log entry.
|
||||||
|
//
|
||||||
|
// This is used in the 1.0 release of gcp/observability, and thus must not be
|
||||||
|
// deleted or changed.
|
||||||
|
type LogEntryConfig interface {
|
||||||
|
toProto() *binlogpb.GrpcLogEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientHeader configs the binary log entry to be a ClientHeader entry.
|
||||||
|
type ClientHeader struct {
|
||||||
|
OnClientSide bool
|
||||||
|
Header metadata.MD
|
||||||
|
MethodName string
|
||||||
|
Authority string
|
||||||
|
Timeout time.Duration
|
||||||
|
// PeerAddr is required only when it's on server side.
|
||||||
|
PeerAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientHeader) toProto() *binlogpb.GrpcLogEntry {
|
||||||
|
// This function doesn't need to set all the fields (e.g. seq ID). The Log
|
||||||
|
// function will set the fields when necessary.
|
||||||
|
clientHeader := &binlogpb.ClientHeader{
|
||||||
|
Metadata: mdToMetadataProto(c.Header),
|
||||||
|
MethodName: c.MethodName,
|
||||||
|
Authority: c.Authority,
|
||||||
|
}
|
||||||
|
if c.Timeout > 0 {
|
||||||
|
clientHeader.Timeout = durationpb.New(c.Timeout)
|
||||||
|
}
|
||||||
|
ret := &binlogpb.GrpcLogEntry{
|
||||||
|
Type: binlogpb.GrpcLogEntry_EVENT_TYPE_CLIENT_HEADER,
|
||||||
|
Payload: &binlogpb.GrpcLogEntry_ClientHeader{
|
||||||
|
ClientHeader: clientHeader,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if c.OnClientSide {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
|
||||||
|
} else {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
|
||||||
|
}
|
||||||
|
if c.PeerAddr != nil {
|
||||||
|
ret.Peer = addrToProto(c.PeerAddr)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerHeader configs the binary log entry to be a ServerHeader entry.
|
||||||
|
type ServerHeader struct {
|
||||||
|
OnClientSide bool
|
||||||
|
Header metadata.MD
|
||||||
|
// PeerAddr is required only when it's on client side.
|
||||||
|
PeerAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerHeader) toProto() *binlogpb.GrpcLogEntry {
|
||||||
|
ret := &binlogpb.GrpcLogEntry{
|
||||||
|
Type: binlogpb.GrpcLogEntry_EVENT_TYPE_SERVER_HEADER,
|
||||||
|
Payload: &binlogpb.GrpcLogEntry_ServerHeader{
|
||||||
|
ServerHeader: &binlogpb.ServerHeader{
|
||||||
|
Metadata: mdToMetadataProto(c.Header),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if c.OnClientSide {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
|
||||||
|
} else {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
|
||||||
|
}
|
||||||
|
if c.PeerAddr != nil {
|
||||||
|
ret.Peer = addrToProto(c.PeerAddr)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientMessage configs the binary log entry to be a ClientMessage entry.
|
||||||
|
type ClientMessage struct {
|
||||||
|
OnClientSide bool
|
||||||
|
// Message can be a proto.Message or []byte. Other messages formats are not
|
||||||
|
// supported.
|
||||||
|
Message any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientMessage) toProto() *binlogpb.GrpcLogEntry {
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if m, ok := c.Message.(proto.Message); ok {
|
||||||
|
data, err = proto.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
grpclogLogger.Infof("binarylogging: failed to marshal proto message: %v", err)
|
||||||
|
}
|
||||||
|
} else if b, ok := c.Message.([]byte); ok {
|
||||||
|
data = b
|
||||||
|
} else {
|
||||||
|
grpclogLogger.Infof("binarylogging: message to log is neither proto.message nor []byte")
|
||||||
|
}
|
||||||
|
ret := &binlogpb.GrpcLogEntry{
|
||||||
|
Type: binlogpb.GrpcLogEntry_EVENT_TYPE_CLIENT_MESSAGE,
|
||||||
|
Payload: &binlogpb.GrpcLogEntry_Message{
|
||||||
|
Message: &binlogpb.Message{
|
||||||
|
Length: uint32(len(data)),
|
||||||
|
Data: data,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if c.OnClientSide {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
|
||||||
|
} else {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerMessage configs the binary log entry to be a ServerMessage entry.
|
||||||
|
type ServerMessage struct {
|
||||||
|
OnClientSide bool
|
||||||
|
// Message can be a proto.Message or []byte. Other messages formats are not
|
||||||
|
// supported.
|
||||||
|
Message any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerMessage) toProto() *binlogpb.GrpcLogEntry {
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if m, ok := c.Message.(proto.Message); ok {
|
||||||
|
data, err = proto.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
grpclogLogger.Infof("binarylogging: failed to marshal proto message: %v", err)
|
||||||
|
}
|
||||||
|
} else if b, ok := c.Message.([]byte); ok {
|
||||||
|
data = b
|
||||||
|
} else {
|
||||||
|
grpclogLogger.Infof("binarylogging: message to log is neither proto.message nor []byte")
|
||||||
|
}
|
||||||
|
ret := &binlogpb.GrpcLogEntry{
|
||||||
|
Type: binlogpb.GrpcLogEntry_EVENT_TYPE_SERVER_MESSAGE,
|
||||||
|
Payload: &binlogpb.GrpcLogEntry_Message{
|
||||||
|
Message: &binlogpb.Message{
|
||||||
|
Length: uint32(len(data)),
|
||||||
|
Data: data,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if c.OnClientSide {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
|
||||||
|
} else {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientHalfClose configs the binary log entry to be a ClientHalfClose entry.
|
||||||
|
type ClientHalfClose struct {
|
||||||
|
OnClientSide bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientHalfClose) toProto() *binlogpb.GrpcLogEntry {
|
||||||
|
ret := &binlogpb.GrpcLogEntry{
|
||||||
|
Type: binlogpb.GrpcLogEntry_EVENT_TYPE_CLIENT_HALF_CLOSE,
|
||||||
|
Payload: nil, // No payload here.
|
||||||
|
}
|
||||||
|
if c.OnClientSide {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
|
||||||
|
} else {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerTrailer configs the binary log entry to be a ServerTrailer entry.
|
||||||
|
type ServerTrailer struct {
|
||||||
|
OnClientSide bool
|
||||||
|
Trailer metadata.MD
|
||||||
|
// Err is the status error.
|
||||||
|
Err error
|
||||||
|
// PeerAddr is required only when it's on client side and the RPC is trailer
|
||||||
|
// only.
|
||||||
|
PeerAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerTrailer) toProto() *binlogpb.GrpcLogEntry {
|
||||||
|
st, ok := status.FromError(c.Err)
|
||||||
|
if !ok {
|
||||||
|
grpclogLogger.Info("binarylogging: error in trailer is not a status error")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
detailsBytes []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
stProto := st.Proto()
|
||||||
|
if stProto != nil && len(stProto.Details) != 0 {
|
||||||
|
detailsBytes, err = proto.Marshal(stProto)
|
||||||
|
if err != nil {
|
||||||
|
grpclogLogger.Infof("binarylogging: failed to marshal status proto: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret := &binlogpb.GrpcLogEntry{
|
||||||
|
Type: binlogpb.GrpcLogEntry_EVENT_TYPE_SERVER_TRAILER,
|
||||||
|
Payload: &binlogpb.GrpcLogEntry_Trailer{
|
||||||
|
Trailer: &binlogpb.Trailer{
|
||||||
|
Metadata: mdToMetadataProto(c.Trailer),
|
||||||
|
StatusCode: uint32(st.Code()),
|
||||||
|
StatusMessage: st.Message(),
|
||||||
|
StatusDetails: detailsBytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if c.OnClientSide {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
|
||||||
|
} else {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
|
||||||
|
}
|
||||||
|
if c.PeerAddr != nil {
|
||||||
|
ret.Peer = addrToProto(c.PeerAddr)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel configs the binary log entry to be a Cancel entry.
|
||||||
|
type Cancel struct {
|
||||||
|
OnClientSide bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cancel) toProto() *binlogpb.GrpcLogEntry {
|
||||||
|
ret := &binlogpb.GrpcLogEntry{
|
||||||
|
Type: binlogpb.GrpcLogEntry_EVENT_TYPE_CANCEL,
|
||||||
|
Payload: nil,
|
||||||
|
}
|
||||||
|
if c.OnClientSide {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
|
||||||
|
} else {
|
||||||
|
ret.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// metadataKeyOmit returns whether the metadata entry with this key should be
|
||||||
|
// omitted.
|
||||||
|
func metadataKeyOmit(key string) bool {
|
||||||
|
switch key {
|
||||||
|
case "lb-token", ":path", ":authority", "content-encoding", "content-type", "user-agent", "te":
|
||||||
|
return true
|
||||||
|
case "grpc-trace-bin": // grpc-trace-bin is special because it's visible to users.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.HasPrefix(key, "grpc-")
|
||||||
|
}
|
||||||
|
|
||||||
|
func mdToMetadataProto(md metadata.MD) *binlogpb.Metadata {
|
||||||
|
ret := &binlogpb.Metadata{}
|
||||||
|
for k, vv := range md {
|
||||||
|
if metadataKeyOmit(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, v := range vv {
|
||||||
|
ret.Entry = append(ret.Entry,
|
||||||
|
&binlogpb.MetadataEntry{
|
||||||
|
Key: k,
|
||||||
|
Value: []byte(v),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func addrToProto(addr net.Addr) *binlogpb.Address {
|
||||||
|
ret := &binlogpb.Address{}
|
||||||
|
switch a := addr.(type) {
|
||||||
|
case *net.TCPAddr:
|
||||||
|
if a.IP.To4() != nil {
|
||||||
|
ret.Type = binlogpb.Address_TYPE_IPV4
|
||||||
|
} else if a.IP.To16() != nil {
|
||||||
|
ret.Type = binlogpb.Address_TYPE_IPV6
|
||||||
|
} else {
|
||||||
|
ret.Type = binlogpb.Address_TYPE_UNKNOWN
|
||||||
|
// Do not set address and port fields.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ret.Address = a.IP.String()
|
||||||
|
ret.IpPort = uint32(a.Port)
|
||||||
|
case *net.UnixAddr:
|
||||||
|
ret.Type = binlogpb.Address_TYPE_UNIX
|
||||||
|
ret.Address = a.String()
|
||||||
|
default:
|
||||||
|
ret.Type = binlogpb.Address_TYPE_UNKNOWN
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2018 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 binarylog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
binlogpb "google.golang.org/grpc/binarylog/grpc_binarylog_v1"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultSink is the sink where the logs will be written to. It's exported
|
||||||
|
// for the binarylog package to update.
|
||||||
|
DefaultSink Sink = &noopSink{} // TODO(blog): change this default (file in /tmp).
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sink writes log entry into the binary log sink.
|
||||||
|
//
|
||||||
|
// sink is a copy of the exported binarylog.Sink, to avoid circular dependency.
|
||||||
|
type Sink interface {
|
||||||
|
// Write will be called to write the log entry into the sink.
|
||||||
|
//
|
||||||
|
// It should be thread-safe so it can be called in parallel.
|
||||||
|
Write(*binlogpb.GrpcLogEntry) error
|
||||||
|
// Close will be called when the Sink is replaced by a new Sink.
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type noopSink struct{}
|
||||||
|
|
||||||
|
func (ns *noopSink) Write(*binlogpb.GrpcLogEntry) error { return nil }
|
||||||
|
func (ns *noopSink) Close() error { return nil }
|
||||||
|
|
||||||
|
// newWriterSink creates a binary log sink with the given writer.
|
||||||
|
//
|
||||||
|
// Write() marshals the proto message and writes it to the given writer. Each
|
||||||
|
// message is prefixed with a 4 byte big endian unsigned integer as the length.
|
||||||
|
//
|
||||||
|
// No buffer is done, Close() doesn't try to close the writer.
|
||||||
|
func newWriterSink(w io.Writer) Sink {
|
||||||
|
return &writerSink{out: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
type writerSink struct {
|
||||||
|
out io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *writerSink) Write(e *binlogpb.GrpcLogEntry) error {
|
||||||
|
b, err := proto.Marshal(e)
|
||||||
|
if err != nil {
|
||||||
|
grpclogLogger.Errorf("binary logging: failed to marshal proto message: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hdr := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(hdr, uint32(len(b)))
|
||||||
|
if _, err := ws.out.Write(hdr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := ws.out.Write(b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *writerSink) Close() error { return nil }
|
||||||
|
|
||||||
|
type bufferedSink struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
closer io.Closer
|
||||||
|
out Sink // out is built on buf.
|
||||||
|
buf *bufio.Writer // buf is kept for flush.
|
||||||
|
flusherStarted bool
|
||||||
|
|
||||||
|
writeTicker *time.Ticker
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *bufferedSink) Write(e *binlogpb.GrpcLogEntry) error {
|
||||||
|
fs.mu.Lock()
|
||||||
|
defer fs.mu.Unlock()
|
||||||
|
if !fs.flusherStarted {
|
||||||
|
// Start the write loop when Write is called.
|
||||||
|
fs.startFlushGoroutine()
|
||||||
|
fs.flusherStarted = true
|
||||||
|
}
|
||||||
|
if err := fs.out.Write(e); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
bufFlushDuration = 60 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func (fs *bufferedSink) startFlushGoroutine() {
|
||||||
|
fs.writeTicker = time.NewTicker(bufFlushDuration)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-fs.done:
|
||||||
|
return
|
||||||
|
case <-fs.writeTicker.C:
|
||||||
|
}
|
||||||
|
fs.mu.Lock()
|
||||||
|
if err := fs.buf.Flush(); err != nil {
|
||||||
|
grpclogLogger.Warningf("failed to flush to Sink: %v", err)
|
||||||
|
}
|
||||||
|
fs.mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *bufferedSink) Close() error {
|
||||||
|
fs.mu.Lock()
|
||||||
|
defer fs.mu.Unlock()
|
||||||
|
if fs.writeTicker != nil {
|
||||||
|
fs.writeTicker.Stop()
|
||||||
|
}
|
||||||
|
close(fs.done)
|
||||||
|
if err := fs.buf.Flush(); err != nil {
|
||||||
|
grpclogLogger.Warningf("failed to flush to Sink: %v", err)
|
||||||
|
}
|
||||||
|
if err := fs.closer.Close(); err != nil {
|
||||||
|
grpclogLogger.Warningf("failed to close the underlying WriterCloser: %v", err)
|
||||||
|
}
|
||||||
|
if err := fs.out.Close(); err != nil {
|
||||||
|
grpclogLogger.Warningf("failed to close the Sink: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBufferedSink creates a binary log sink with the given WriteCloser.
|
||||||
|
//
|
||||||
|
// Write() marshals the proto message and writes it to the given writer. Each
|
||||||
|
// message is prefixed with a 4 byte big endian unsigned integer as the length.
|
||||||
|
//
|
||||||
|
// Content is kept in a buffer, and is flushed every 60 seconds.
|
||||||
|
//
|
||||||
|
// Close closes the WriteCloser.
|
||||||
|
func NewBufferedSink(o io.WriteCloser) Sink {
|
||||||
|
bufW := bufio.NewWriter(o)
|
||||||
|
return &bufferedSink{
|
||||||
|
closer: o,
|
||||||
|
out: newWriterSink(bufW),
|
||||||
|
buf: bufW,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* 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 buffer provides an implementation of an unbounded buffer.
|
||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unbounded is an implementation of an unbounded buffer which does not use
|
||||||
|
// extra goroutines. This is typically used for passing updates from one entity
|
||||||
|
// to another within gRPC.
|
||||||
|
//
|
||||||
|
// All methods on this type are thread-safe and don't block on anything except
|
||||||
|
// the underlying mutex used for synchronization.
|
||||||
|
//
|
||||||
|
// Unbounded supports values of any type to be stored in it by using a channel
|
||||||
|
// of `any`. This means that a call to Put() incurs an extra memory allocation,
|
||||||
|
// and also that users need a type assertion while reading. For performance
|
||||||
|
// critical code paths, using Unbounded is strongly discouraged and defining a
|
||||||
|
// new type specific implementation of this buffer is preferred. See
|
||||||
|
// internal/transport/transport.go for an example of this.
|
||||||
|
type Unbounded struct {
|
||||||
|
c chan any
|
||||||
|
closed bool
|
||||||
|
closing bool
|
||||||
|
mu sync.Mutex
|
||||||
|
backlog []any
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUnbounded returns a new instance of Unbounded.
|
||||||
|
func NewUnbounded() *Unbounded {
|
||||||
|
return &Unbounded{c: make(chan any, 1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errBufferClosed = errors.New("Put called on closed buffer.Unbounded")
|
||||||
|
|
||||||
|
// Put adds t to the unbounded buffer.
|
||||||
|
func (b *Unbounded) Put(t any) error {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
if b.closing {
|
||||||
|
return errBufferClosed
|
||||||
|
}
|
||||||
|
if len(b.backlog) == 0 {
|
||||||
|
select {
|
||||||
|
case b.c <- t:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.backlog = append(b.backlog, t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load sends the earliest buffered data, if any, onto the read channel returned
|
||||||
|
// by Get(). Users are expected to call this every time they successfully read a
|
||||||
|
// value from the read channel.
|
||||||
|
func (b *Unbounded) Load() {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
if len(b.backlog) > 0 {
|
||||||
|
select {
|
||||||
|
case b.c <- b.backlog[0]:
|
||||||
|
b.backlog[0] = nil
|
||||||
|
b.backlog = b.backlog[1:]
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
} else if b.closing && !b.closed {
|
||||||
|
b.closed = true
|
||||||
|
close(b.c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a read channel on which values added to the buffer, via Put(),
|
||||||
|
// are sent on.
|
||||||
|
//
|
||||||
|
// Upon reading a value from this channel, users are expected to call Load() to
|
||||||
|
// send the next buffered value onto the channel if there is any.
|
||||||
|
//
|
||||||
|
// If the unbounded buffer is closed, the read channel returned by this method
|
||||||
|
// is closed after all data is drained.
|
||||||
|
func (b *Unbounded) Get() <-chan any {
|
||||||
|
return b.c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the unbounded buffer. No subsequent data may be Put(), and the
|
||||||
|
// channel returned from Get() will be closed after all the data is read and
|
||||||
|
// Load() is called for the final time.
|
||||||
|
func (b *Unbounded) Close() {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
if b.closing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.closing = true
|
||||||
|
if len(b.backlog) == 0 {
|
||||||
|
b.closed = true
|
||||||
|
close(b.c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,270 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 channelz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Channel represents a channel within channelz, which includes metrics and
|
||||||
|
// internal channelz data, such as channelz id, child list, etc.
|
||||||
|
type Channel struct {
|
||||||
|
Entity
|
||||||
|
// ID is the channelz id of this channel.
|
||||||
|
ID int64
|
||||||
|
// RefName is the human readable reference string of this channel.
|
||||||
|
RefName string
|
||||||
|
|
||||||
|
closeCalled bool
|
||||||
|
nestedChans map[int64]string
|
||||||
|
subChans map[int64]string
|
||||||
|
Parent *Channel
|
||||||
|
trace *ChannelTrace
|
||||||
|
// traceRefCount is the number of trace events that reference this channel.
|
||||||
|
// Non-zero traceRefCount means the trace of this channel cannot be deleted.
|
||||||
|
traceRefCount int32
|
||||||
|
|
||||||
|
// ChannelMetrics holds connectivity state, target and call metrics for the
|
||||||
|
// channel within channelz.
|
||||||
|
ChannelMetrics ChannelMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implemented to make Channel implement the Identifier interface used for
|
||||||
|
// nesting.
|
||||||
|
func (c *Channel) channelzIdentifier() {}
|
||||||
|
|
||||||
|
// String returns a string representation of the Channel, including its parent
|
||||||
|
// entity and ID.
|
||||||
|
func (c *Channel) String() string {
|
||||||
|
if c.Parent == nil {
|
||||||
|
return fmt.Sprintf("Channel #%d", c.ID)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s Channel #%d", c.Parent, c.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) id() int64 {
|
||||||
|
return c.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubChans returns a copy of the map of sub-channels associated with the
|
||||||
|
// Channel.
|
||||||
|
func (c *Channel) SubChans() map[int64]string {
|
||||||
|
db.mu.RLock()
|
||||||
|
defer db.mu.RUnlock()
|
||||||
|
return copyMap(c.subChans)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NestedChans returns a copy of the map of nested channels associated with the
|
||||||
|
// Channel.
|
||||||
|
func (c *Channel) NestedChans() map[int64]string {
|
||||||
|
db.mu.RLock()
|
||||||
|
defer db.mu.RUnlock()
|
||||||
|
return copyMap(c.nestedChans)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace returns a copy of the Channel's trace data.
|
||||||
|
func (c *Channel) Trace() *ChannelTrace {
|
||||||
|
db.mu.RLock()
|
||||||
|
defer db.mu.RUnlock()
|
||||||
|
return c.trace.copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelMetrics holds connectivity state, target and call metrics for the
|
||||||
|
// channel within channelz.
|
||||||
|
type ChannelMetrics struct {
|
||||||
|
// The current connectivity state of the channel.
|
||||||
|
State atomic.Pointer[connectivity.State]
|
||||||
|
// The target this channel originally tried to connect to. May be absent
|
||||||
|
Target atomic.Pointer[string]
|
||||||
|
// The number of calls started on the channel.
|
||||||
|
CallsStarted atomic.Int64
|
||||||
|
// The number of calls that have completed with an OK status.
|
||||||
|
CallsSucceeded atomic.Int64
|
||||||
|
// The number of calls that have a completed with a non-OK status.
|
||||||
|
CallsFailed atomic.Int64
|
||||||
|
// The last time a call was started on the channel.
|
||||||
|
LastCallStartedTimestamp atomic.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFrom copies the metrics in o to c. For testing only.
|
||||||
|
func (c *ChannelMetrics) CopyFrom(o *ChannelMetrics) {
|
||||||
|
c.State.Store(o.State.Load())
|
||||||
|
c.Target.Store(o.Target.Load())
|
||||||
|
c.CallsStarted.Store(o.CallsStarted.Load())
|
||||||
|
c.CallsSucceeded.Store(o.CallsSucceeded.Load())
|
||||||
|
c.CallsFailed.Store(o.CallsFailed.Load())
|
||||||
|
c.LastCallStartedTimestamp.Store(o.LastCallStartedTimestamp.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true iff the metrics of c are the same as the metrics of o.
|
||||||
|
// For testing only.
|
||||||
|
func (c *ChannelMetrics) Equal(o any) bool {
|
||||||
|
oc, ok := o.(*ChannelMetrics)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (c.State.Load() == nil) != (oc.State.Load() == nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c.State.Load() != nil && *c.State.Load() != *oc.State.Load() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (c.Target.Load() == nil) != (oc.Target.Load() == nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c.Target.Load() != nil && *c.Target.Load() != *oc.Target.Load() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return c.CallsStarted.Load() == oc.CallsStarted.Load() &&
|
||||||
|
c.CallsFailed.Load() == oc.CallsFailed.Load() &&
|
||||||
|
c.CallsSucceeded.Load() == oc.CallsSucceeded.Load() &&
|
||||||
|
c.LastCallStartedTimestamp.Load() == oc.LastCallStartedTimestamp.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func strFromPointer(s *string) string {
|
||||||
|
if s == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the ChannelMetrics, including its
|
||||||
|
// state, target, and call metrics.
|
||||||
|
func (c *ChannelMetrics) String() string {
|
||||||
|
return fmt.Sprintf("State: %v, Target: %s, CallsStarted: %v, CallsSucceeded: %v, CallsFailed: %v, LastCallStartedTimestamp: %v",
|
||||||
|
c.State.Load(), strFromPointer(c.Target.Load()), c.CallsStarted.Load(), c.CallsSucceeded.Load(), c.CallsFailed.Load(), c.LastCallStartedTimestamp.Load(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChannelMetricForTesting creates a new instance of ChannelMetrics with
|
||||||
|
// specified initial values for testing purposes.
|
||||||
|
func NewChannelMetricForTesting(state connectivity.State, target string, started, succeeded, failed, timestamp int64) *ChannelMetrics {
|
||||||
|
c := &ChannelMetrics{}
|
||||||
|
c.State.Store(&state)
|
||||||
|
c.Target.Store(&target)
|
||||||
|
c.CallsStarted.Store(started)
|
||||||
|
c.CallsSucceeded.Store(succeeded)
|
||||||
|
c.CallsFailed.Store(failed)
|
||||||
|
c.LastCallStartedTimestamp.Store(timestamp)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) addChild(id int64, e entry) {
|
||||||
|
switch v := e.(type) {
|
||||||
|
case *SubChannel:
|
||||||
|
c.subChans[id] = v.RefName
|
||||||
|
case *Channel:
|
||||||
|
c.nestedChans[id] = v.RefName
|
||||||
|
default:
|
||||||
|
logger.Errorf("cannot add a child (id = %d) of type %T to a channel", id, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) deleteChild(id int64) {
|
||||||
|
delete(c.subChans, id)
|
||||||
|
delete(c.nestedChans, id)
|
||||||
|
c.deleteSelfIfReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) triggerDelete() {
|
||||||
|
c.closeCalled = true
|
||||||
|
c.deleteSelfIfReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) getParentID() int64 {
|
||||||
|
if c.Parent == nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return c.Parent.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSelfFromTree tries to delete the channel from the channelz entry relation tree, which means
|
||||||
|
// deleting the channel reference from its parent's child list.
|
||||||
|
//
|
||||||
|
// In order for a channel to be deleted from the tree, it must meet the criteria that, removal of the
|
||||||
|
// corresponding grpc object has been invoked, and the channel does not have any children left.
|
||||||
|
//
|
||||||
|
// The returned boolean value indicates whether the channel has been successfully deleted from tree.
|
||||||
|
func (c *Channel) deleteSelfFromTree() (deleted bool) {
|
||||||
|
if !c.closeCalled || len(c.subChans)+len(c.nestedChans) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// not top channel
|
||||||
|
if c.Parent != nil {
|
||||||
|
c.Parent.deleteChild(c.ID)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSelfFromMap checks whether it is valid to delete the channel from the map, which means
|
||||||
|
// deleting the channel from channelz's tracking entirely. Users can no longer use id to query the
|
||||||
|
// channel, and its memory will be garbage collected.
|
||||||
|
//
|
||||||
|
// The trace reference count of the channel must be 0 in order to be deleted from the map. This is
|
||||||
|
// specified in the channel tracing gRFC that as long as some other trace has reference to an entity,
|
||||||
|
// the trace of the referenced entity must not be deleted. In order to release the resource allocated
|
||||||
|
// by grpc, the reference to the grpc object is reset to a dummy object.
|
||||||
|
//
|
||||||
|
// deleteSelfFromMap must be called after deleteSelfFromTree returns true.
|
||||||
|
//
|
||||||
|
// It returns a bool to indicate whether the channel can be safely deleted from map.
|
||||||
|
func (c *Channel) deleteSelfFromMap() (delete bool) {
|
||||||
|
return c.getTraceRefCount() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSelfIfReady tries to delete the channel itself from the channelz database.
|
||||||
|
// The delete process includes two steps:
|
||||||
|
// 1. delete the channel from the entry relation tree, i.e. delete the channel reference from its
|
||||||
|
// parent's child list.
|
||||||
|
// 2. delete the channel from the map, i.e. delete the channel entirely from channelz. Lookup by id
|
||||||
|
// will return entry not found error.
|
||||||
|
func (c *Channel) deleteSelfIfReady() {
|
||||||
|
if !c.deleteSelfFromTree() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !c.deleteSelfFromMap() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db.deleteEntry(c.ID)
|
||||||
|
c.trace.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) getChannelTrace() *ChannelTrace {
|
||||||
|
return c.trace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) incrTraceRefCount() {
|
||||||
|
atomic.AddInt32(&c.traceRefCount, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) decrTraceRefCount() {
|
||||||
|
atomic.AddInt32(&c.traceRefCount, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) getTraceRefCount() int {
|
||||||
|
i := atomic.LoadInt32(&c.traceRefCount)
|
||||||
|
return int(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Channel) getRefName() string {
|
||||||
|
return c.RefName
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,395 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2018 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 channelz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// entry represents a node in the channelz database.
|
||||||
|
type entry interface {
|
||||||
|
// addChild adds a child e, whose channelz id is id to child list
|
||||||
|
addChild(id int64, e entry)
|
||||||
|
// deleteChild deletes a child with channelz id to be id from child list
|
||||||
|
deleteChild(id int64)
|
||||||
|
// triggerDelete tries to delete self from channelz database. However, if
|
||||||
|
// child list is not empty, then deletion from the database is on hold until
|
||||||
|
// the last child is deleted from database.
|
||||||
|
triggerDelete()
|
||||||
|
// deleteSelfIfReady check whether triggerDelete() has been called before,
|
||||||
|
// and whether child list is now empty. If both conditions are met, then
|
||||||
|
// delete self from database.
|
||||||
|
deleteSelfIfReady()
|
||||||
|
// getParentID returns parent ID of the entry. 0 value parent ID means no parent.
|
||||||
|
getParentID() int64
|
||||||
|
Entity
|
||||||
|
}
|
||||||
|
|
||||||
|
// channelMap is the storage data structure for channelz.
|
||||||
|
//
|
||||||
|
// Methods of channelMap can be divided into two categories with respect to
|
||||||
|
// locking.
|
||||||
|
//
|
||||||
|
// 1. Methods acquire the global lock.
|
||||||
|
// 2. Methods that can only be called when global lock is held.
|
||||||
|
//
|
||||||
|
// A second type of method need always to be called inside a first type of method.
|
||||||
|
type channelMap struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
topLevelChannels map[int64]struct{}
|
||||||
|
channels map[int64]*Channel
|
||||||
|
subChannels map[int64]*SubChannel
|
||||||
|
sockets map[int64]*Socket
|
||||||
|
servers map[int64]*Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func newChannelMap() *channelMap {
|
||||||
|
return &channelMap{
|
||||||
|
topLevelChannels: make(map[int64]struct{}),
|
||||||
|
channels: make(map[int64]*Channel),
|
||||||
|
subChannels: make(map[int64]*SubChannel),
|
||||||
|
sockets: make(map[int64]*Socket),
|
||||||
|
servers: make(map[int64]*Server),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) addServer(id int64, s *Server) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
s.cm = c
|
||||||
|
c.servers[id] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) addChannel(id int64, cn *Channel, isTopChannel bool, pid int64) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
cn.trace.cm = c
|
||||||
|
c.channels[id] = cn
|
||||||
|
if isTopChannel {
|
||||||
|
c.topLevelChannels[id] = struct{}{}
|
||||||
|
} else if p := c.channels[pid]; p != nil {
|
||||||
|
p.addChild(id, cn)
|
||||||
|
} else {
|
||||||
|
logger.Infof("channel %d references invalid parent ID %d", id, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) addSubChannel(id int64, sc *SubChannel, pid int64) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
sc.trace.cm = c
|
||||||
|
c.subChannels[id] = sc
|
||||||
|
if p := c.channels[pid]; p != nil {
|
||||||
|
p.addChild(id, sc)
|
||||||
|
} else {
|
||||||
|
logger.Infof("subchannel %d references invalid parent ID %d", id, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) addSocket(s *Socket) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
s.cm = c
|
||||||
|
c.sockets[s.ID] = s
|
||||||
|
if s.Parent == nil {
|
||||||
|
logger.Infof("normal socket %d has no parent", s.ID)
|
||||||
|
}
|
||||||
|
s.Parent.(entry).addChild(s.ID, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeEntry triggers the removal of an entry, which may not indeed delete the
|
||||||
|
// entry, if it has to wait on the deletion of its children and until no other
|
||||||
|
// entity's channel trace references it. It may lead to a chain of entry
|
||||||
|
// deletion. For example, deleting the last socket of a gracefully shutting down
|
||||||
|
// server will lead to the server being also deleted.
|
||||||
|
func (c *channelMap) removeEntry(id int64) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.findEntry(id).triggerDelete()
|
||||||
|
}
|
||||||
|
|
||||||
|
// tracedChannel represents tracing operations which are present on both
|
||||||
|
// channels and subChannels.
|
||||||
|
type tracedChannel interface {
|
||||||
|
getChannelTrace() *ChannelTrace
|
||||||
|
incrTraceRefCount()
|
||||||
|
decrTraceRefCount()
|
||||||
|
getRefName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// c.mu must be held by the caller
|
||||||
|
func (c *channelMap) decrTraceRefCount(id int64) {
|
||||||
|
e := c.findEntry(id)
|
||||||
|
if v, ok := e.(tracedChannel); ok {
|
||||||
|
v.decrTraceRefCount()
|
||||||
|
e.deleteSelfIfReady()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// c.mu must be held by the caller.
|
||||||
|
func (c *channelMap) findEntry(id int64) entry {
|
||||||
|
if v, ok := c.channels[id]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if v, ok := c.subChannels[id]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if v, ok := c.servers[id]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if v, ok := c.sockets[id]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return &dummyEntry{idNotFound: id}
|
||||||
|
}
|
||||||
|
|
||||||
|
// c.mu must be held by the caller
|
||||||
|
//
|
||||||
|
// deleteEntry deletes an entry from the channelMap. Before calling this method,
|
||||||
|
// caller must check this entry is ready to be deleted, i.e removeEntry() has
|
||||||
|
// been called on it, and no children still exist.
|
||||||
|
func (c *channelMap) deleteEntry(id int64) entry {
|
||||||
|
if v, ok := c.sockets[id]; ok {
|
||||||
|
delete(c.sockets, id)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if v, ok := c.subChannels[id]; ok {
|
||||||
|
delete(c.subChannels, id)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if v, ok := c.channels[id]; ok {
|
||||||
|
delete(c.channels, id)
|
||||||
|
delete(c.topLevelChannels, id)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if v, ok := c.servers[id]; ok {
|
||||||
|
delete(c.servers, id)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return &dummyEntry{idNotFound: id}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) traceEvent(id int64, desc *TraceEvent) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
child := c.findEntry(id)
|
||||||
|
childTC, ok := child.(tracedChannel)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
childTC.getChannelTrace().append(&traceEvent{Desc: desc.Desc, Severity: desc.Severity, Timestamp: time.Now()})
|
||||||
|
if desc.Parent != nil {
|
||||||
|
parent := c.findEntry(child.getParentID())
|
||||||
|
var chanType RefChannelType
|
||||||
|
switch child.(type) {
|
||||||
|
case *Channel:
|
||||||
|
chanType = RefChannel
|
||||||
|
case *SubChannel:
|
||||||
|
chanType = RefSubChannel
|
||||||
|
}
|
||||||
|
if parentTC, ok := parent.(tracedChannel); ok {
|
||||||
|
parentTC.getChannelTrace().append(&traceEvent{
|
||||||
|
Desc: desc.Parent.Desc,
|
||||||
|
Severity: desc.Parent.Severity,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
RefID: id,
|
||||||
|
RefName: childTC.getRefName(),
|
||||||
|
RefType: chanType,
|
||||||
|
})
|
||||||
|
childTC.incrTraceRefCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type int64Slice []int64
|
||||||
|
|
||||||
|
func (s int64Slice) Len() int { return len(s) }
|
||||||
|
func (s int64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s int64Slice) Less(i, j int) bool { return s[i] < s[j] }
|
||||||
|
|
||||||
|
func copyMap(m map[int64]string) map[int64]string {
|
||||||
|
n := make(map[int64]string)
|
||||||
|
for k, v := range m {
|
||||||
|
n[k] = v
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) getTopChannels(id int64, maxResults int) ([]*Channel, bool) {
|
||||||
|
if maxResults <= 0 {
|
||||||
|
maxResults = EntriesPerPage
|
||||||
|
}
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
l := int64(len(c.topLevelChannels))
|
||||||
|
ids := make([]int64, 0, l)
|
||||||
|
|
||||||
|
for k := range c.topLevelChannels {
|
||||||
|
ids = append(ids, k)
|
||||||
|
}
|
||||||
|
sort.Sort(int64Slice(ids))
|
||||||
|
idx := sort.Search(len(ids), func(i int) bool { return ids[i] >= id })
|
||||||
|
end := true
|
||||||
|
var t []*Channel
|
||||||
|
for _, v := range ids[idx:] {
|
||||||
|
if len(t) == maxResults {
|
||||||
|
end = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if cn, ok := c.channels[v]; ok {
|
||||||
|
t = append(t, cn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t, end
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) getServers(id int64, maxResults int) ([]*Server, bool) {
|
||||||
|
if maxResults <= 0 {
|
||||||
|
maxResults = EntriesPerPage
|
||||||
|
}
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
ids := make([]int64, 0, len(c.servers))
|
||||||
|
for k := range c.servers {
|
||||||
|
ids = append(ids, k)
|
||||||
|
}
|
||||||
|
sort.Sort(int64Slice(ids))
|
||||||
|
idx := sort.Search(len(ids), func(i int) bool { return ids[i] >= id })
|
||||||
|
end := true
|
||||||
|
var s []*Server
|
||||||
|
for _, v := range ids[idx:] {
|
||||||
|
if len(s) == maxResults {
|
||||||
|
end = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if svr, ok := c.servers[v]; ok {
|
||||||
|
s = append(s, svr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s, end
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) getServerSockets(id int64, startID int64, maxResults int) ([]*Socket, bool) {
|
||||||
|
if maxResults <= 0 {
|
||||||
|
maxResults = EntriesPerPage
|
||||||
|
}
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
svr, ok := c.servers[id]
|
||||||
|
if !ok {
|
||||||
|
// server with id doesn't exist.
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
svrskts := svr.sockets
|
||||||
|
ids := make([]int64, 0, len(svrskts))
|
||||||
|
sks := make([]*Socket, 0, min(len(svrskts), maxResults))
|
||||||
|
for k := range svrskts {
|
||||||
|
ids = append(ids, k)
|
||||||
|
}
|
||||||
|
sort.Sort(int64Slice(ids))
|
||||||
|
idx := sort.Search(len(ids), func(i int) bool { return ids[i] >= startID })
|
||||||
|
end := true
|
||||||
|
for _, v := range ids[idx:] {
|
||||||
|
if len(sks) == maxResults {
|
||||||
|
end = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ns, ok := c.sockets[v]; ok {
|
||||||
|
sks = append(sks, ns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sks, end
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) getChannel(id int64) *Channel {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return c.channels[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) getSubChannel(id int64) *SubChannel {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return c.subChannels[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) getSocket(id int64) *Socket {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return c.sockets[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channelMap) getServer(id int64) *Server {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
return c.servers[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummyEntry struct {
|
||||||
|
// dummyEntry is a fake entry to handle entry not found case.
|
||||||
|
idNotFound int64
|
||||||
|
Entity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyEntry) String() string {
|
||||||
|
return fmt.Sprintf("non-existent entity #%d", d.idNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyEntry) ID() int64 { return d.idNotFound }
|
||||||
|
|
||||||
|
func (d *dummyEntry) addChild(id int64, e entry) {
|
||||||
|
// Note: It is possible for a normal program to reach here under race
|
||||||
|
// condition. For example, there could be a race between ClientConn.Close()
|
||||||
|
// info being propagated to addrConn and http2Client. ClientConn.Close()
|
||||||
|
// cancel the context and result in http2Client to error. The error info is
|
||||||
|
// then caught by transport monitor and before addrConn.tearDown() is called
|
||||||
|
// in side ClientConn.Close(). Therefore, the addrConn will create a new
|
||||||
|
// transport. And when registering the new transport in channelz, its parent
|
||||||
|
// addrConn could have already been torn down and deleted from channelz
|
||||||
|
// tracking, and thus reach the code here.
|
||||||
|
logger.Infof("attempt to add child of type %T with id %d to a parent (id=%d) that doesn't currently exist", e, id, d.idNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyEntry) deleteChild(id int64) {
|
||||||
|
// It is possible for a normal program to reach here under race condition.
|
||||||
|
// Refer to the example described in addChild().
|
||||||
|
logger.Infof("attempt to delete child with id %d from a parent (id=%d) that doesn't currently exist", id, d.idNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyEntry) triggerDelete() {
|
||||||
|
logger.Warningf("attempt to delete an entry (id=%d) that doesn't currently exist", d.idNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*dummyEntry) deleteSelfIfReady() {
|
||||||
|
// code should not reach here. deleteSelfIfReady is always called on an existing entry.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*dummyEntry) getParentID() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entity is implemented by all channelz types.
|
||||||
|
type Entity interface {
|
||||||
|
isEntity()
|
||||||
|
fmt.Stringer
|
||||||
|
id() int64
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2018 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 channelz defines internal APIs for enabling channelz service, entry
|
||||||
|
// registration/deletion, and accessing channelz data. It also defines channelz
|
||||||
|
// metric struct formats.
|
||||||
|
package channelz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// IDGen is the global channelz entity ID generator. It should not be used
|
||||||
|
// outside this package except by tests.
|
||||||
|
IDGen IDGenerator
|
||||||
|
|
||||||
|
db = newChannelMap()
|
||||||
|
// EntriesPerPage defines the number of channelz entries to be shown on a web page.
|
||||||
|
EntriesPerPage = 50
|
||||||
|
curState int32
|
||||||
|
)
|
||||||
|
|
||||||
|
// TurnOn turns on channelz data collection.
|
||||||
|
func TurnOn() {
|
||||||
|
atomic.StoreInt32(&curState, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
internal.ChannelzTurnOffForTesting = func() {
|
||||||
|
atomic.StoreInt32(&curState, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOn returns whether channelz data collection is on.
|
||||||
|
func IsOn() bool {
|
||||||
|
return atomic.LoadInt32(&curState) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTopChannels returns a slice of top channel's ChannelMetric, along with a
|
||||||
|
// boolean indicating whether there's more top channels to be queried for.
|
||||||
|
//
|
||||||
|
// The arg id specifies that only top channel with id at or above it will be
|
||||||
|
// included in the result. The returned slice is up to a length of the arg
|
||||||
|
// maxResults or EntriesPerPage if maxResults is zero, and is sorted in ascending
|
||||||
|
// id order.
|
||||||
|
func GetTopChannels(id int64, maxResults int) ([]*Channel, bool) {
|
||||||
|
return db.getTopChannels(id, maxResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServers returns a slice of server's ServerMetric, along with a
|
||||||
|
// boolean indicating whether there's more servers to be queried for.
|
||||||
|
//
|
||||||
|
// The arg id specifies that only server with id at or above it will be included
|
||||||
|
// in the result. The returned slice is up to a length of the arg maxResults or
|
||||||
|
// EntriesPerPage if maxResults is zero, and is sorted in ascending id order.
|
||||||
|
func GetServers(id int64, maxResults int) ([]*Server, bool) {
|
||||||
|
return db.getServers(id, maxResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServerSockets returns a slice of server's (identified by id) normal socket's
|
||||||
|
// SocketMetrics, along with a boolean indicating whether there's more sockets to
|
||||||
|
// be queried for.
|
||||||
|
//
|
||||||
|
// The arg startID specifies that only sockets with id at or above it will be
|
||||||
|
// included in the result. The returned slice is up to a length of the arg maxResults
|
||||||
|
// or EntriesPerPage if maxResults is zero, and is sorted in ascending id order.
|
||||||
|
func GetServerSockets(id int64, startID int64, maxResults int) ([]*Socket, bool) {
|
||||||
|
return db.getServerSockets(id, startID, maxResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChannel returns the Channel for the channel (identified by id).
|
||||||
|
func GetChannel(id int64) *Channel {
|
||||||
|
return db.getChannel(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubChannel returns the SubChannel for the subchannel (identified by id).
|
||||||
|
func GetSubChannel(id int64) *SubChannel {
|
||||||
|
return db.getSubChannel(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSocket returns the Socket for the socket (identified by id).
|
||||||
|
func GetSocket(id int64) *Socket {
|
||||||
|
return db.getSocket(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServer returns the ServerMetric for the server (identified by id).
|
||||||
|
func GetServer(id int64) *Server {
|
||||||
|
return db.getServer(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterChannel registers the given channel c in the channelz database with
|
||||||
|
// target as its target and reference name, and adds it to the child list of its
|
||||||
|
// parent. parent == nil means no parent.
|
||||||
|
//
|
||||||
|
// Returns a unique channelz identifier assigned to this channel.
|
||||||
|
//
|
||||||
|
// If channelz is not turned ON, the channelz database is not mutated.
|
||||||
|
func RegisterChannel(parent *Channel, target string) *Channel {
|
||||||
|
id := IDGen.genID()
|
||||||
|
|
||||||
|
if !IsOn() {
|
||||||
|
return &Channel{ID: id}
|
||||||
|
}
|
||||||
|
|
||||||
|
isTopChannel := parent == nil
|
||||||
|
|
||||||
|
cn := &Channel{
|
||||||
|
ID: id,
|
||||||
|
RefName: target,
|
||||||
|
nestedChans: make(map[int64]string),
|
||||||
|
subChans: make(map[int64]string),
|
||||||
|
Parent: parent,
|
||||||
|
trace: &ChannelTrace{CreationTime: time.Now(), Events: make([]*traceEvent, 0, getMaxTraceEntry())},
|
||||||
|
}
|
||||||
|
cn.ChannelMetrics.Target.Store(&target)
|
||||||
|
db.addChannel(id, cn, isTopChannel, cn.getParentID())
|
||||||
|
return cn
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterSubChannel registers the given subChannel c in the channelz database
|
||||||
|
// with ref as its reference name, and adds it to the child list of its parent
|
||||||
|
// (identified by pid).
|
||||||
|
//
|
||||||
|
// Returns a unique channelz identifier assigned to this subChannel.
|
||||||
|
//
|
||||||
|
// If channelz is not turned ON, the channelz database is not mutated.
|
||||||
|
func RegisterSubChannel(parent *Channel, ref string) *SubChannel {
|
||||||
|
id := IDGen.genID()
|
||||||
|
sc := &SubChannel{
|
||||||
|
ID: id,
|
||||||
|
RefName: ref,
|
||||||
|
parent: parent,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsOn() {
|
||||||
|
return sc
|
||||||
|
}
|
||||||
|
|
||||||
|
sc.sockets = make(map[int64]string)
|
||||||
|
sc.trace = &ChannelTrace{CreationTime: time.Now(), Events: make([]*traceEvent, 0, getMaxTraceEntry())}
|
||||||
|
db.addSubChannel(id, sc, parent.ID)
|
||||||
|
return sc
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterServer registers the given server s in channelz database. It returns
|
||||||
|
// the unique channelz tracking id assigned to this server.
|
||||||
|
//
|
||||||
|
// If channelz is not turned ON, the channelz database is not mutated.
|
||||||
|
func RegisterServer(ref string) *Server {
|
||||||
|
id := IDGen.genID()
|
||||||
|
if !IsOn() {
|
||||||
|
return &Server{ID: id}
|
||||||
|
}
|
||||||
|
|
||||||
|
svr := &Server{
|
||||||
|
RefName: ref,
|
||||||
|
sockets: make(map[int64]string),
|
||||||
|
listenSockets: make(map[int64]string),
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
db.addServer(id, svr)
|
||||||
|
return svr
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterSocket registers the given normal socket s in channelz database
|
||||||
|
// with ref as its reference name, and adds it to the child list of its parent
|
||||||
|
// (identified by skt.Parent, which must be set). It returns the unique channelz
|
||||||
|
// tracking id assigned to this normal socket.
|
||||||
|
//
|
||||||
|
// If channelz is not turned ON, the channelz database is not mutated.
|
||||||
|
func RegisterSocket(skt *Socket) *Socket {
|
||||||
|
skt.ID = IDGen.genID()
|
||||||
|
if IsOn() {
|
||||||
|
db.addSocket(skt)
|
||||||
|
}
|
||||||
|
return skt
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveEntry removes an entry with unique channelz tracking id to be id from
|
||||||
|
// channelz database.
|
||||||
|
//
|
||||||
|
// If channelz is not turned ON, this function is a no-op.
|
||||||
|
func RemoveEntry(id int64) {
|
||||||
|
if !IsOn() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db.removeEntry(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDGenerator is an incrementing atomic that tracks IDs for channelz entities.
|
||||||
|
type IDGenerator struct {
|
||||||
|
id int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the generated ID back to zero. Should only be used at
|
||||||
|
// initialization or by tests sensitive to the ID number.
|
||||||
|
func (i *IDGenerator) Reset() {
|
||||||
|
atomic.StoreInt64(&i.id, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IDGenerator) genID() int64 {
|
||||||
|
return atomic.AddInt64(&i.id, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier is an opaque channelz identifier used to expose channelz symbols
|
||||||
|
// outside of grpc. Currently only implemented by Channel since no other
|
||||||
|
// types require exposure outside grpc.
|
||||||
|
type Identifier interface {
|
||||||
|
Entity
|
||||||
|
channelzIdentifier()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 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 channelz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = grpclog.Component("channelz")
|
||||||
|
|
||||||
|
// Info logs and adds a trace event if channelz is on.
|
||||||
|
func Info(l grpclog.DepthLoggerV2, e Entity, args ...any) {
|
||||||
|
AddTraceEvent(l, e, 1, &TraceEvent{
|
||||||
|
Desc: fmt.Sprint(args...),
|
||||||
|
Severity: CtInfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs and adds a trace event if channelz is on.
|
||||||
|
func Infof(l grpclog.DepthLoggerV2, e Entity, format string, args ...any) {
|
||||||
|
AddTraceEvent(l, e, 1, &TraceEvent{
|
||||||
|
Desc: fmt.Sprintf(format, args...),
|
||||||
|
Severity: CtInfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs and adds a trace event if channelz is on.
|
||||||
|
func Warning(l grpclog.DepthLoggerV2, e Entity, args ...any) {
|
||||||
|
AddTraceEvent(l, e, 1, &TraceEvent{
|
||||||
|
Desc: fmt.Sprint(args...),
|
||||||
|
Severity: CtWarning,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf logs and adds a trace event if channelz is on.
|
||||||
|
func Warningf(l grpclog.DepthLoggerV2, e Entity, format string, args ...any) {
|
||||||
|
AddTraceEvent(l, e, 1, &TraceEvent{
|
||||||
|
Desc: fmt.Sprintf(format, args...),
|
||||||
|
Severity: CtWarning,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs and adds a trace event if channelz is on.
|
||||||
|
func Error(l grpclog.DepthLoggerV2, e Entity, args ...any) {
|
||||||
|
AddTraceEvent(l, e, 1, &TraceEvent{
|
||||||
|
Desc: fmt.Sprint(args...),
|
||||||
|
Severity: CtError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs and adds a trace event if channelz is on.
|
||||||
|
func Errorf(l grpclog.DepthLoggerV2, e Entity, format string, args ...any) {
|
||||||
|
AddTraceEvent(l, e, 1, &TraceEvent{
|
||||||
|
Desc: fmt.Sprintf(format, args...),
|
||||||
|
Severity: CtError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 channelz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server is the channelz representation of a server.
|
||||||
|
type Server struct {
|
||||||
|
Entity
|
||||||
|
ID int64
|
||||||
|
RefName string
|
||||||
|
|
||||||
|
ServerMetrics ServerMetrics
|
||||||
|
|
||||||
|
closeCalled bool
|
||||||
|
sockets map[int64]string
|
||||||
|
listenSockets map[int64]string
|
||||||
|
cm *channelMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerMetrics defines a struct containing metrics for servers.
|
||||||
|
type ServerMetrics struct {
|
||||||
|
// The number of incoming calls started on the server.
|
||||||
|
CallsStarted atomic.Int64
|
||||||
|
// The number of incoming calls that have completed with an OK status.
|
||||||
|
CallsSucceeded atomic.Int64
|
||||||
|
// The number of incoming calls that have a completed with a non-OK status.
|
||||||
|
CallsFailed atomic.Int64
|
||||||
|
// The last time a call was started on the server.
|
||||||
|
LastCallStartedTimestamp atomic.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServerMetricsForTesting returns an initialized ServerMetrics.
|
||||||
|
func NewServerMetricsForTesting(started, succeeded, failed, timestamp int64) *ServerMetrics {
|
||||||
|
sm := &ServerMetrics{}
|
||||||
|
sm.CallsStarted.Store(started)
|
||||||
|
sm.CallsSucceeded.Store(succeeded)
|
||||||
|
sm.CallsFailed.Store(failed)
|
||||||
|
sm.LastCallStartedTimestamp.Store(timestamp)
|
||||||
|
return sm
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFrom copies the metrics data from the provided ServerMetrics
|
||||||
|
// instance into the current instance.
|
||||||
|
func (sm *ServerMetrics) CopyFrom(o *ServerMetrics) {
|
||||||
|
sm.CallsStarted.Store(o.CallsStarted.Load())
|
||||||
|
sm.CallsSucceeded.Store(o.CallsSucceeded.Load())
|
||||||
|
sm.CallsFailed.Store(o.CallsFailed.Load())
|
||||||
|
sm.LastCallStartedTimestamp.Store(o.LastCallStartedTimestamp.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenSockets returns the listening sockets for s.
|
||||||
|
func (s *Server) ListenSockets() map[int64]string {
|
||||||
|
db.mu.RLock()
|
||||||
|
defer db.mu.RUnlock()
|
||||||
|
return copyMap(s.listenSockets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a printable description of s.
|
||||||
|
func (s *Server) String() string {
|
||||||
|
return fmt.Sprintf("Server #%d", s.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) id() int64 {
|
||||||
|
return s.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) addChild(id int64, e entry) {
|
||||||
|
switch v := e.(type) {
|
||||||
|
case *Socket:
|
||||||
|
switch v.SocketType {
|
||||||
|
case SocketTypeNormal:
|
||||||
|
s.sockets[id] = v.RefName
|
||||||
|
case SocketTypeListen:
|
||||||
|
s.listenSockets[id] = v.RefName
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
logger.Errorf("cannot add a child (id = %d) of type %T to a server", id, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) deleteChild(id int64) {
|
||||||
|
delete(s.sockets, id)
|
||||||
|
delete(s.listenSockets, id)
|
||||||
|
s.deleteSelfIfReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) triggerDelete() {
|
||||||
|
s.closeCalled = true
|
||||||
|
s.deleteSelfIfReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) deleteSelfIfReady() {
|
||||||
|
if !s.closeCalled || len(s.sockets)+len(s.listenSockets) != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.cm.deleteEntry(s.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getParentID() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 channelz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SocketMetrics defines the struct that the implementor of Socket interface
|
||||||
|
// should return from ChannelzMetric().
|
||||||
|
type SocketMetrics struct {
|
||||||
|
// The number of streams that have been started.
|
||||||
|
StreamsStarted atomic.Int64
|
||||||
|
// The number of streams that have ended successfully:
|
||||||
|
// On client side, receiving frame with eos bit set.
|
||||||
|
// On server side, sending frame with eos bit set.
|
||||||
|
StreamsSucceeded atomic.Int64
|
||||||
|
// The number of streams that have ended unsuccessfully:
|
||||||
|
// On client side, termination without receiving frame with eos bit set.
|
||||||
|
// On server side, termination without sending frame with eos bit set.
|
||||||
|
StreamsFailed atomic.Int64
|
||||||
|
// The number of messages successfully sent on this socket.
|
||||||
|
MessagesSent atomic.Int64
|
||||||
|
MessagesReceived atomic.Int64
|
||||||
|
// The number of keep alives sent. This is typically implemented with HTTP/2
|
||||||
|
// ping messages.
|
||||||
|
KeepAlivesSent atomic.Int64
|
||||||
|
// The last time a stream was created by this endpoint. Usually unset for
|
||||||
|
// servers.
|
||||||
|
LastLocalStreamCreatedTimestamp atomic.Int64
|
||||||
|
// The last time a stream was created by the remote endpoint. Usually unset
|
||||||
|
// for clients.
|
||||||
|
LastRemoteStreamCreatedTimestamp atomic.Int64
|
||||||
|
// The last time a message was sent by this endpoint.
|
||||||
|
LastMessageSentTimestamp atomic.Int64
|
||||||
|
// The last time a message was received by this endpoint.
|
||||||
|
LastMessageReceivedTimestamp atomic.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// EphemeralSocketMetrics are metrics that change rapidly and are tracked
|
||||||
|
// outside of channelz.
|
||||||
|
type EphemeralSocketMetrics struct {
|
||||||
|
// The amount of window, granted to the local endpoint by the remote endpoint.
|
||||||
|
// This may be slightly out of date due to network latency. This does NOT
|
||||||
|
// include stream level or TCP level flow control info.
|
||||||
|
LocalFlowControlWindow int64
|
||||||
|
// The amount of window, granted to the remote endpoint by the local endpoint.
|
||||||
|
// This may be slightly out of date due to network latency. This does NOT
|
||||||
|
// include stream level or TCP level flow control info.
|
||||||
|
RemoteFlowControlWindow int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// SocketType represents the type of socket.
|
||||||
|
type SocketType string
|
||||||
|
|
||||||
|
// SocketType can be one of these.
|
||||||
|
const (
|
||||||
|
SocketTypeNormal = "NormalSocket"
|
||||||
|
SocketTypeListen = "ListenSocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Socket represents a socket within channelz which includes socket
|
||||||
|
// metrics and data related to socket activity and provides methods
|
||||||
|
// for managing and interacting with sockets.
|
||||||
|
type Socket struct {
|
||||||
|
Entity
|
||||||
|
SocketType SocketType
|
||||||
|
ID int64
|
||||||
|
Parent Entity
|
||||||
|
cm *channelMap
|
||||||
|
SocketMetrics SocketMetrics
|
||||||
|
EphemeralMetrics func() *EphemeralSocketMetrics
|
||||||
|
|
||||||
|
RefName string
|
||||||
|
// The locally bound address. Immutable.
|
||||||
|
LocalAddr net.Addr
|
||||||
|
// The remote bound address. May be absent. Immutable.
|
||||||
|
RemoteAddr net.Addr
|
||||||
|
// Optional, represents the name of the remote endpoint, if different than
|
||||||
|
// the original target name. Immutable.
|
||||||
|
RemoteName string
|
||||||
|
// Immutable.
|
||||||
|
SocketOptions *SocketOptionData
|
||||||
|
// Immutable.
|
||||||
|
Security credentials.ChannelzSecurityValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the Socket, including its parent
|
||||||
|
// entity, socket type, and ID.
|
||||||
|
func (ls *Socket) String() string {
|
||||||
|
return fmt.Sprintf("%s %s #%d", ls.Parent, ls.SocketType, ls.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *Socket) id() int64 {
|
||||||
|
return ls.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *Socket) addChild(id int64, e entry) {
|
||||||
|
logger.Errorf("cannot add a child (id = %d) of type %T to a listen socket", id, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *Socket) deleteChild(id int64) {
|
||||||
|
logger.Errorf("cannot delete a child (id = %d) from a listen socket", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *Socket) triggerDelete() {
|
||||||
|
ls.cm.deleteEntry(ls.ID)
|
||||||
|
ls.Parent.(entry).deleteChild(ls.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *Socket) deleteSelfIfReady() {
|
||||||
|
logger.Errorf("cannot call deleteSelfIfReady on a listen socket")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *Socket) getParentID() int64 {
|
||||||
|
return ls.Parent.id()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 channelz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SubChannel is the channelz representation of a subchannel.
|
||||||
|
type SubChannel struct {
|
||||||
|
Entity
|
||||||
|
// ID is the channelz id of this subchannel.
|
||||||
|
ID int64
|
||||||
|
// RefName is the human readable reference string of this subchannel.
|
||||||
|
RefName string
|
||||||
|
closeCalled bool
|
||||||
|
sockets map[int64]string
|
||||||
|
parent *Channel
|
||||||
|
trace *ChannelTrace
|
||||||
|
traceRefCount int32
|
||||||
|
|
||||||
|
ChannelMetrics ChannelMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SubChannel) String() string {
|
||||||
|
return fmt.Sprintf("%s SubChannel #%d", sc.parent, sc.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SubChannel) id() int64 {
|
||||||
|
return sc.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sockets returns a copy of the sockets map associated with the SubChannel.
|
||||||
|
func (sc *SubChannel) Sockets() map[int64]string {
|
||||||
|
db.mu.RLock()
|
||||||
|
defer db.mu.RUnlock()
|
||||||
|
return copyMap(sc.sockets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace returns a copy of the ChannelTrace associated with the SubChannel.
|
||||||
|
func (sc *SubChannel) Trace() *ChannelTrace {
|
||||||
|
db.mu.RLock()
|
||||||
|
defer db.mu.RUnlock()
|
||||||
|
return sc.trace.copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SubChannel) addChild(id int64, e entry) {
|
||||||
|
if v, ok := e.(*Socket); ok && v.SocketType == SocketTypeNormal {
|
||||||
|
sc.sockets[id] = v.RefName
|
||||||
|
} else {
|
||||||
|
logger.Errorf("cannot add a child (id = %d) of type %T to a subChannel", id, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SubChannel) deleteChild(id int64) {
|
||||||
|
delete(sc.sockets, id)
|
||||||
|
sc.deleteSelfIfReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SubChannel) triggerDelete() {
|
||||||
|
sc.closeCalled = true
|
||||||
|
sc.deleteSelfIfReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SubChannel) getParentID() int64 {
|
||||||
|
return sc.parent.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSelfFromTree tries to delete the subchannel from the channelz entry relation tree, which
|
||||||
|
// means deleting the subchannel reference from its parent's child list.
|
||||||
|
//
|
||||||
|
// In order for a subchannel to be deleted from the tree, it must meet the criteria that, removal of
|
||||||
|
// the corresponding grpc object has been invoked, and the subchannel does not have any children left.
|
||||||
|
//
|
||||||
|
// The returned boolean value indicates whether the channel has been successfully deleted from tree.
|
||||||
|
func (sc *SubChannel) deleteSelfFromTree() (deleted bool) {
|
||||||
|
if !sc.closeCalled || len(sc.sockets) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sc.parent.deleteChild(sc.ID)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSelfFromMap checks whether it is valid to delete the subchannel from the map, which means
|
||||||
|
// deleting the subchannel from channelz's tracking entirely. Users can no longer use id to query
|
||||||
|
// the subchannel, and its memory will be garbage collected.
|
||||||
|
//
|
||||||
|
// The trace reference count of the subchannel must be 0 in order to be deleted from the map. This is
|
||||||
|
// specified in the channel tracing gRFC that as long as some other trace has reference to an entity,
|
||||||
|
// the trace of the referenced entity must not be deleted. In order to release the resource allocated
|
||||||
|
// by grpc, the reference to the grpc object is reset to a dummy object.
|
||||||
|
//
|
||||||
|
// deleteSelfFromMap must be called after deleteSelfFromTree returns true.
|
||||||
|
//
|
||||||
|
// It returns a bool to indicate whether the channel can be safely deleted from map.
|
||||||
|
func (sc *SubChannel) deleteSelfFromMap() (delete bool) {
|
||||||
|
return sc.getTraceRefCount() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSelfIfReady tries to delete the subchannel itself from the channelz database.
|
||||||
|
// The delete process includes two steps:
|
||||||
|
// 1. delete the subchannel from the entry relation tree, i.e. delete the subchannel reference from
|
||||||
|
// its parent's child list.
|
||||||
|
// 2. delete the subchannel from the map, i.e. delete the subchannel entirely from channelz. Lookup
|
||||||
|
// by id will return entry not found error.
|
||||||
|
func (sc *SubChannel) deleteSelfIfReady() {
|
||||||
|
if !sc.deleteSelfFromTree() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !sc.deleteSelfFromMap() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db.deleteEntry(sc.ID)
|
||||||
|
sc.trace.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SubChannel) getChannelTrace() *ChannelTrace {
|
||||||
|
return sc.trace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SubChannel) incrTraceRefCount() {
|
||||||
|
atomic.AddInt32(&sc.traceRefCount, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SubChannel) decrTraceRefCount() {
|
||||||
|
atomic.AddInt32(&sc.traceRefCount, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SubChannel) getTraceRefCount() int {
|
||||||
|
i := atomic.LoadInt32(&sc.traceRefCount)
|
||||||
|
return int(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SubChannel) getRefName() string {
|
||||||
|
return sc.RefName
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2018 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 channelz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SocketOptionData defines the struct to hold socket option data, and related
|
||||||
|
// getter function to obtain info from fd.
|
||||||
|
type SocketOptionData struct {
|
||||||
|
Linger *unix.Linger
|
||||||
|
RecvTimeout *unix.Timeval
|
||||||
|
SendTimeout *unix.Timeval
|
||||||
|
TCPInfo *unix.TCPInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getsockopt defines the function to get socket options requested by channelz.
|
||||||
|
// It is to be passed to syscall.RawConn.Control().
|
||||||
|
func (s *SocketOptionData) Getsockopt(fd uintptr) {
|
||||||
|
if v, err := unix.GetsockoptLinger(int(fd), syscall.SOL_SOCKET, syscall.SO_LINGER); err == nil {
|
||||||
|
s.Linger = v
|
||||||
|
}
|
||||||
|
if v, err := unix.GetsockoptTimeval(int(fd), syscall.SOL_SOCKET, syscall.SO_RCVTIMEO); err == nil {
|
||||||
|
s.RecvTimeout = v
|
||||||
|
}
|
||||||
|
if v, err := unix.GetsockoptTimeval(int(fd), syscall.SOL_SOCKET, syscall.SO_SNDTIMEO); err == nil {
|
||||||
|
s.SendTimeout = v
|
||||||
|
}
|
||||||
|
if v, err := unix.GetsockoptTCPInfo(int(fd), syscall.SOL_TCP, syscall.TCP_INFO); err == nil {
|
||||||
|
s.TCPInfo = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSocketOption gets the socket option info of the conn.
|
||||||
|
func GetSocketOption(socket any) *SocketOptionData {
|
||||||
|
c, ok := socket.(syscall.Conn)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data := &SocketOptionData{}
|
||||||
|
if rawConn, err := c.SyscallConn(); err == nil {
|
||||||
|
rawConn.Control(data.Getsockopt)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue