google.golang.org/grpc@v1.74.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) ExitIdle() { 57 if o.sc != nil { 58 o.sc.Connect() 59 } 60 } 61 62 func (o *orcab) UpdateClientConnState(s balancer.ClientConnState) error { 63 if o.sc != nil { 64 o.sc.UpdateAddresses(s.ResolverState.Addresses) 65 return nil 66 } 67 68 if len(s.ResolverState.Addresses) == 0 { 69 o.ResolverError(fmt.Errorf("produced no addresses")) 70 return fmt.Errorf("resolver produced no addresses") 71 } 72 var err error 73 o.sc, err = o.cc.NewSubConn(s.ResolverState.Addresses, balancer.NewSubConnOptions{StateListener: o.updateSubConnState}) 74 if err != nil { 75 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.TransientFailure, Picker: base.NewErrPicker(fmt.Errorf("error creating subconn: %v", err))}) 76 return nil 77 } 78 o.sc.Connect() 79 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.Connecting, Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable)}) 80 return nil 81 } 82 83 func (o *orcab) ResolverError(err error) { 84 if o.sc == nil { 85 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.TransientFailure, Picker: base.NewErrPicker(fmt.Errorf("resolver error: %v", err))}) 86 } 87 } 88 89 func (o *orcab) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) { 90 logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state) 91 } 92 93 func (o *orcab) updateSubConnState(state balancer.SubConnState) { 94 switch state.ConnectivityState { 95 case connectivity.Ready: 96 orca.RegisterOOBListener(o.sc, o, orca.OOBListenerOptions{ReportInterval: time.Second}) 97 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.Ready, Picker: &orcaPicker{o: o}}) 98 case connectivity.TransientFailure: 99 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.TransientFailure, Picker: base.NewErrPicker(fmt.Errorf("all subchannels in transient failure: %v", state.ConnectionError))}) 100 case connectivity.Connecting: 101 // Ignore; picker already set to "connecting". 102 case connectivity.Idle: 103 o.sc.Connect() 104 o.cc.UpdateState(balancer.State{ConnectivityState: connectivity.Connecting, Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable)}) 105 case connectivity.Shutdown: 106 // Ignore; we are closing but handle that in Close instead. 107 } 108 } 109 110 func (o *orcab) Close() {} 111 112 func (o *orcab) OnLoadReport(r *v3orcapb.OrcaLoadReport) { 113 o.reportMu.Lock() 114 defer o.reportMu.Unlock() 115 logger.Infof("received OOB load report: %v", r) 116 o.report = r 117 } 118 119 type orcaPicker struct { 120 o *orcab 121 } 122 123 func (p *orcaPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 124 doneCB := func(di balancer.DoneInfo) { 125 if lr, _ := di.ServerLoad.(*v3orcapb.OrcaLoadReport); lr != nil && 126 (lr.CpuUtilization != 0 || lr.MemUtilization != 0 || len(lr.Utilization) > 0 || len(lr.RequestCost) > 0) { 127 // Since all RPCs will respond with a load report due to the 128 // presence of the DialOption, we need to inspect every field and 129 // use the out-of-band report instead if all are unset/zero. 130 setContextCMR(info.Ctx, lr) 131 } else { 132 p.o.reportMu.Lock() 133 defer p.o.reportMu.Unlock() 134 if lr := p.o.report; lr != nil { 135 setContextCMR(info.Ctx, lr) 136 } 137 } 138 } 139 return balancer.PickResult{SubConn: p.o.sc, Done: doneCB}, nil 140 } 141 142 func setContextCMR(ctx context.Context, lr *v3orcapb.OrcaLoadReport) { 143 if r := orcaResultFromContext(ctx); r != nil { 144 *r = lr 145 } 146 } 147 148 type orcaKey string 149 150 var orcaCtxKey = orcaKey("orcaResult") 151 152 // contextWithORCAResult sets a key in ctx with a pointer to an ORCA load 153 // report that is to be filled in by the "test_backend_metrics_load_balancer" 154 // LB policy's Picker's Done callback. 155 // 156 // If a per-call load report is provided from the server for the call, result 157 // will be filled with that, otherwise the most recent OOB load report is used. 158 // If no OOB report has been received, result is not modified. 159 func contextWithORCAResult(ctx context.Context, result **v3orcapb.OrcaLoadReport) context.Context { 160 return context.WithValue(ctx, orcaCtxKey, result) 161 } 162 163 // orcaResultFromContext returns the ORCA load report stored in the context. 164 // The LB policy uses this to communicate the load report back to the interop 165 // client application. 166 func orcaResultFromContext(ctx context.Context) **v3orcapb.OrcaLoadReport { 167 v := ctx.Value(orcaCtxKey) 168 if v == nil { 169 return nil 170 } 171 return v.(**v3orcapb.OrcaLoadReport) 172 }