google.golang.org/grpc@v1.74.2/health/server_internal_test.go (about)

     1  /*
     2   *
     3   * Copyright 2018 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 health
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/google/go-cmp/cmp"
    30  	"google.golang.org/grpc/codes"
    31  	"google.golang.org/grpc/status"
    32  	"google.golang.org/protobuf/testing/protocmp"
    33  
    34  	healthpb "google.golang.org/grpc/health/grpc_health_v1"
    35  	"google.golang.org/grpc/internal/grpctest"
    36  )
    37  
    38  type s struct {
    39  	grpctest.Tester
    40  }
    41  
    42  func Test(t *testing.T) {
    43  	grpctest.RunSubTests(t, s{})
    44  }
    45  
    46  func (s) TestShutdown(t *testing.T) {
    47  	const testService = "tteesstt"
    48  	s := NewServer()
    49  	s.SetServingStatus(testService, healthpb.HealthCheckResponse_SERVING)
    50  
    51  	status := s.statusMap[testService]
    52  	if status != healthpb.HealthCheckResponse_SERVING {
    53  		t.Fatalf("status for %s is %v, want %v", testService, status, healthpb.HealthCheckResponse_SERVING)
    54  	}
    55  
    56  	var wg sync.WaitGroup
    57  	wg.Add(2)
    58  	// Run SetServingStatus and Shutdown in parallel.
    59  	go func() {
    60  		for i := 0; i < 1000; i++ {
    61  			s.SetServingStatus(testService, healthpb.HealthCheckResponse_SERVING)
    62  			time.Sleep(time.Microsecond)
    63  		}
    64  		wg.Done()
    65  	}()
    66  	go func() {
    67  		time.Sleep(300 * time.Microsecond)
    68  		s.Shutdown()
    69  		wg.Done()
    70  	}()
    71  	wg.Wait()
    72  
    73  	s.mu.Lock()
    74  	status = s.statusMap[testService]
    75  	s.mu.Unlock()
    76  	if status != healthpb.HealthCheckResponse_NOT_SERVING {
    77  		t.Fatalf("status for %s is %v, want %v", testService, status, healthpb.HealthCheckResponse_NOT_SERVING)
    78  	}
    79  
    80  	s.Resume()
    81  	status = s.statusMap[testService]
    82  	if status != healthpb.HealthCheckResponse_SERVING {
    83  		t.Fatalf("status for %s is %v, want %v", testService, status, healthpb.HealthCheckResponse_SERVING)
    84  	}
    85  
    86  	s.SetServingStatus(testService, healthpb.HealthCheckResponse_NOT_SERVING)
    87  	status = s.statusMap[testService]
    88  	if status != healthpb.HealthCheckResponse_NOT_SERVING {
    89  		t.Fatalf("status for %s is %v, want %v", testService, status, healthpb.HealthCheckResponse_NOT_SERVING)
    90  	}
    91  }
    92  
    93  // TestList verifies that List() returns the health status of all the services if no. of services are within
    94  // maxAllowedLimits.
    95  func (s) TestList(t *testing.T) {
    96  	s := NewServer()
    97  
    98  	// Fill out status map with information
    99  	const length = 3
   100  	for i := 0; i < length; i++ {
   101  		s.SetServingStatus(fmt.Sprintf("%d", i),
   102  			healthpb.HealthCheckResponse_ServingStatus(i))
   103  	}
   104  
   105  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   106  	defer cancel()
   107  	var in healthpb.HealthListRequest
   108  	got, err := s.List(ctx, &in)
   109  
   110  	if err != nil {
   111  		t.Fatalf("s.List(ctx, &in) returned err %v, want nil", err)
   112  	}
   113  	if len(got.GetStatuses()) != length+1 {
   114  		t.Fatalf("len(out.GetStatuses()) = %d, want %d",
   115  			len(got.GetStatuses()), length+1)
   116  	}
   117  	want := &healthpb.HealthListResponse{
   118  		Statuses: map[string]*healthpb.HealthCheckResponse{
   119  			"":  {Status: healthpb.HealthCheckResponse_SERVING},
   120  			"0": {Status: healthpb.HealthCheckResponse_UNKNOWN},
   121  			"1": {Status: healthpb.HealthCheckResponse_SERVING},
   122  			"2": {Status: healthpb.HealthCheckResponse_NOT_SERVING},
   123  		},
   124  	}
   125  	if diff := cmp.Diff(got, want, protocmp.Transform()); diff != "" {
   126  		t.Fatalf("Health response did not match expectation.  Diff (-got, +want): %s", diff)
   127  	}
   128  }
   129  
   130  // TestListResourceExhausted verifies that List()
   131  // returns a ResourceExhausted error if no. of services are more than
   132  // maxAllowedServices.
   133  func (s) TestListResourceExhausted(t *testing.T) {
   134  	s := NewServer()
   135  
   136  	// Fill out status map with service information,
   137  	// 101 (100 + 1 existing) elements will trigger an error.
   138  	for i := 1; i <= maxAllowedServices; i++ {
   139  		s.SetServingStatus(fmt.Sprintf("%d", i),
   140  			healthpb.HealthCheckResponse_SERVING)
   141  	}
   142  
   143  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   144  	defer cancel()
   145  	var in healthpb.HealthListRequest
   146  	_, err := s.List(ctx, &in)
   147  
   148  	want := status.Errorf(codes.ResourceExhausted,
   149  		"server health list exceeds maximum capacity: %d", maxAllowedServices)
   150  	if !errors.Is(err, want) {
   151  		t.Fatalf("s.List(ctx, &in) returned %v, want %v", err, want)
   152  	}
   153  }