google.golang.org/grpc@v1.72.2/interop/orcalb.go (about) 1 /* 2 * 3 * Copyright 2023 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package interop 20 21 import ( 22 "context" 23 "fmt" 24 "sync" 25 "time" 26 27 v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3" 28 "google.golang.org/grpc/balancer" 29 "google.golang.org/grpc/balancer/base" 30 "google.golang.org/grpc/connectivity" 31 "google.golang.org/grpc/orca" 32 ) 33 34 func init() { 35 balancer.Register(orcabb{}) 36 } 37 38 type orcabb struct{} 39 40 func (orcabb) Build(cc balancer.ClientConn, _ balancer.BuildOptions) balancer.Balancer { 41 return &orcab{cc: cc} 42 } 43 44 func (orcabb) Name() string { 45 return "test_backend_metrics_load_balancer" 46 } 47 48 type orcab struct { 49 cc balancer.ClientConn 50 sc balancer.SubConn 51 52 reportMu sync.Mutex 53 report *v3orcapb.OrcaLoadReport 54 } 55 56 func (o *orcab) UpdateClientConnState(s balancer.ClientConnState) error { 57 if o.sc != nil { 58 o.sc.UpdateAddresses(s.ResolverState.Addresses) 59 return nil 60 } 61 62 if len(s.ResolverState.Addresses) == 0 { 63 o.ResolverError(fmt.Errorf("produced no addresses")) 64 return fmt.Errorf("resolver produced no addresses") 65 } 66 var err error 67 o.sc, err = o.cc.NewSubConn(s.ResolverState.Addresses, balancer.NewSubConnOptions{StateListener: o.updateSubConnState}) 68 if err != nil { 69 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.TransientFailure, Picker: base.NewErrPicker(fmt.Errorf("error creating subconn: %v", err))}) 70 return nil 71 } 72 o.sc.Connect() 73 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.Connecting, Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable)}) 74 return nil 75 } 76 77 func (o *orcab) ResolverError(err error) { 78 if o.sc == nil { 79 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.TransientFailure, Picker: base.NewErrPicker(fmt.Errorf("resolver error: %v", err))}) 80 } 81 } 82 83 func (o *orcab) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) { 84 logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state) 85 } 86 87 func (o *orcab) updateSubConnState(state balancer.SubConnState) { 88 switch state.ConnectivityState { 89 case connectivity.Ready: 90 orca.RegisterOOBListener(o.sc, o, orca.OOBListenerOptions{ReportInterval: time.Second}) 91 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.Ready, Picker: &orcaPicker{o: o}}) 92 case connectivity.TransientFailure: 93 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.TransientFailure, Picker: base.NewErrPicker(fmt.Errorf("all subchannels in transient failure: %v", state.ConnectionError))}) 94 case connectivity.Connecting: 95 // Ignore; picker already set to "connecting". 96 case connectivity.Idle: 97 o.sc.Connect() 98 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.Connecting, Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable)}) 99 case connectivity.Shutdown: 100 // Ignore; we are closing but handle that in Close instead. 101 } 102 } 103 104 func (o *orcab) Close() {} 105 106 func (o *orcab) OnLoadReport(r *v3orcapb.OrcaLoadReport) { 107 o.reportMu.Lock() 108 defer o.reportMu.Unlock() 109 logger.Infof("received OOB load report: %v", r) 110 o.report = r 111 } 112 113 type orcaPicker struct { 114 o *orcab 115 } 116 117 func (p *orcaPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 118 doneCB := func(di balancer.DoneInfo) { 119 if lr, _ := di.ServerLoad.(*v3orcapb.OrcaLoadReport); lr != nil && 120 (lr.CpuUtilization != 0 || lr.MemUtilization != 0 || len(lr.Utilization) > 0 || len(lr.RequestCost) > 0) { 121 // Since all RPCs will respond with a load report due to the 122 // presence of the DialOption, we need to inspect every field and 123 // use the out-of-band report instead if all are unset/zero. 124 setContextCMR(info.Ctx, lr) 125 } else { 126 p.o.reportMu.Lock() 127 defer p.o.reportMu.Unlock() 128 if lr := p.o.report; lr != nil { 129 setContextCMR(info.Ctx, lr) 130 } 131 } 132 } 133 return balancer.PickResult{SubConn: p.o.sc, Done: doneCB}, nil 134 } 135 136 func setContextCMR(ctx context.Context, lr *v3orcapb.OrcaLoadReport) { 137 if r := orcaResultFromContext(ctx); r != nil { 138 *r = lr 139 } 140 } 141 142 type orcaKey string 143 144 var orcaCtxKey = orcaKey("orcaResult") 145 146 // contextWithORCAResult sets a key in ctx with a pointer to an ORCA load 147 // report that is to be filled in by the "test_backend_metrics_load_balancer" 148 // LB policy's Picker's Done callback. 149 // 150 // If a per-call load report is provided from the server for the call, result 151 // will be filled with that, otherwise the most recent OOB load report is used. 152 // If no OOB report has been received, result is not modified. 153 func contextWithORCAResult(ctx context.Context, result **v3orcapb.OrcaLoadReport) context.Context { 154 return context.WithValue(ctx, orcaCtxKey, result) 155 } 156 157 // orcaResultFromContext returns the ORCA load report stored in the context. 158 // The LB policy uses this to communicate the load report back to the interop 159 // client application. 160 func orcaResultFromContext(ctx context.Context) **v3orcapb.OrcaLoadReport { 161 v := ctx.Value(orcaCtxKey) 162 if v == nil { 163 return nil 164 } 165 return v.(**v3orcapb.OrcaLoadReport) 166 }