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  }