google.golang.org/grpc@v1.72.2/orca/service_test.go (about)

     1  /*
     2   *
     3   * Copyright 2022 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 orca_test
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"sync/atomic"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	"google.golang.org/grpc"
    30  	"google.golang.org/grpc/credentials/insecure"
    31  	"google.golang.org/grpc/internal/pretty"
    32  	"google.golang.org/grpc/internal/stubserver"
    33  	"google.golang.org/grpc/internal/testutils"
    34  	"google.golang.org/grpc/orca"
    35  	"google.golang.org/grpc/orca/internal"
    36  	"google.golang.org/protobuf/proto"
    37  	"google.golang.org/protobuf/types/known/durationpb"
    38  
    39  	v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
    40  	v3orcaservicegrpc "github.com/cncf/xds/go/xds/service/orca/v3"
    41  	v3orcaservicepb "github.com/cncf/xds/go/xds/service/orca/v3"
    42  	testgrpc "google.golang.org/grpc/interop/grpc_testing"
    43  	testpb "google.golang.org/grpc/interop/grpc_testing"
    44  )
    45  
    46  const requestsMetricKey = "test-service-requests"
    47  
    48  // TestE2E_CustomBackendMetrics_OutOfBand tests the injection of out-of-band
    49  // custom backend metrics from the server application, and verifies that
    50  // expected load reports are received at the client.
    51  //
    52  // TODO: Change this test to use the client API, when ready, to read the
    53  // out-of-band metrics pushed by the server.
    54  func (s) TestE2E_CustomBackendMetrics_OutOfBand(t *testing.T) {
    55  	lis, err := testutils.LocalTCPListener()
    56  	if err != nil {
    57  		t.Fatal(err)
    58  	}
    59  
    60  	// Override the min reporting interval in the internal package.
    61  	const shortReportingInterval = 10 * time.Millisecond
    62  	smr := orca.NewServerMetricsRecorder()
    63  	opts := orca.ServiceOptions{MinReportingInterval: shortReportingInterval, ServerMetricsProvider: smr}
    64  	internal.AllowAnyMinReportingInterval.(func(*orca.ServiceOptions))(&opts)
    65  
    66  	var requests atomic.Int64
    67  
    68  	stub := &stubserver.StubServer{
    69  		Listener: lis,
    70  		UnaryCallF: func(ctx context.Context, req *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
    71  			newRequests := requests.Add(1)
    72  
    73  			smr.SetNamedUtilization(requestsMetricKey, float64(newRequests)*0.01)
    74  			smr.SetCPUUtilization(50.0)
    75  			smr.SetMemoryUtilization(0.9)
    76  			smr.SetApplicationUtilization(1.2)
    77  			return &testpb.SimpleResponse{}, nil
    78  		},
    79  		EmptyCallF: func(ctx context.Context, req *testpb.Empty) (*testpb.Empty, error) {
    80  			smr.DeleteNamedUtilization(requestsMetricKey)
    81  			smr.SetCPUUtilization(0)
    82  			smr.SetMemoryUtilization(0)
    83  			smr.DeleteApplicationUtilization()
    84  			return &testpb.Empty{}, nil
    85  		},
    86  	}
    87  
    88  	// Assign the gRPC server to the stub server and start serving.
    89  	stub.S = grpc.NewServer()
    90  	// Register the OpenRCAService with a very short metrics reporting interval.
    91  	if err := orca.Register(stub.S, opts); err != nil {
    92  		t.Fatalf("orca.EnableOutOfBandMetricsReportingForTesting() failed: %v", err)
    93  	}
    94  	stubserver.StartTestService(t, stub)
    95  	defer stub.S.Stop()
    96  	t.Logf("Started gRPC server at %s...", lis.Addr().String())
    97  
    98  	// Dial the test server.
    99  	cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   100  	if err != nil {
   101  		t.Fatalf("grpc.NewClient(%s) failed: %v", lis.Addr().String(), err)
   102  	}
   103  	defer cc.Close()
   104  
   105  	// Spawn a goroutine which sends 20 unary RPCs to the stub server. This
   106  	// will trigger the injection of custom backend metrics from the
   107  	// stubServer.
   108  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   109  	defer cancel()
   110  	testStub := testgrpc.NewTestServiceClient(cc)
   111  	const numRequests = 20
   112  	errCh := make(chan error, 1)
   113  	go func() {
   114  		for i := 0; i < numRequests; i++ {
   115  			if _, err := testStub.UnaryCall(ctx, &testpb.SimpleRequest{}); err != nil {
   116  				errCh <- fmt.Errorf("UnaryCall failed: %v", err)
   117  				return
   118  			}
   119  			time.Sleep(time.Millisecond)
   120  		}
   121  		errCh <- nil
   122  	}()
   123  
   124  	// Start the server streaming RPC to receive custom backend metrics.
   125  	oobStub := v3orcaservicegrpc.NewOpenRcaServiceClient(cc)
   126  	stream, err := oobStub.StreamCoreMetrics(ctx, &v3orcaservicepb.OrcaLoadReportRequest{ReportInterval: durationpb.New(shortReportingInterval)})
   127  	if err != nil {
   128  		t.Fatalf("Failed to create a stream for out-of-band metrics")
   129  	}
   130  
   131  	// Wait for the server to push metrics which indicate the completion of all
   132  	// the unary RPCs made from the above goroutine.
   133  	for {
   134  		select {
   135  		case <-ctx.Done():
   136  			t.Fatal("Timeout when waiting for out-of-band custom backend metrics to match expected values")
   137  		case err := <-errCh:
   138  			if err != nil {
   139  				t.Fatal(err)
   140  			}
   141  		default:
   142  		}
   143  
   144  		wantProto := &v3orcapb.OrcaLoadReport{
   145  			CpuUtilization:         50.0,
   146  			MemUtilization:         0.9,
   147  			ApplicationUtilization: 1.2,
   148  			Utilization:            map[string]float64{requestsMetricKey: numRequests * 0.01},
   149  		}
   150  		gotProto, err := stream.Recv()
   151  		if err != nil {
   152  			t.Fatalf("Recv() failed: %v", err)
   153  		}
   154  		if !cmp.Equal(gotProto, wantProto, cmp.Comparer(proto.Equal)) {
   155  			t.Logf("Received load report from stream: %s, want: %s", pretty.ToJSON(gotProto), pretty.ToJSON(wantProto))
   156  			continue
   157  		}
   158  		// This means that we received the metrics which we expected.
   159  		break
   160  	}
   161  
   162  	// The EmptyCall RPC is expected to delete earlier injected metrics.
   163  	if _, err := testStub.EmptyCall(ctx, &testpb.Empty{}); err != nil {
   164  		t.Fatalf("EmptyCall failed: %v", err)
   165  	}
   166  	// Wait for the server to push empty metrics which indicate the processing
   167  	// of the above EmptyCall RPC.
   168  	for {
   169  		select {
   170  		case <-ctx.Done():
   171  			t.Fatal("Timeout when waiting for out-of-band custom backend metrics to match expected values")
   172  		default:
   173  		}
   174  
   175  		wantProto := &v3orcapb.OrcaLoadReport{}
   176  		gotProto, err := stream.Recv()
   177  		if err != nil {
   178  			t.Fatalf("Recv() failed: %v", err)
   179  		}
   180  		if !cmp.Equal(gotProto, wantProto, cmp.Comparer(proto.Equal)) {
   181  			t.Logf("Received load report from stream: %s, want: %s", pretty.ToJSON(gotProto), pretty.ToJSON(wantProto))
   182  			continue
   183  		}
   184  		// This means that we received the metrics which we expected.
   185  		break
   186  	}
   187  }