github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/server/admin_cluster_test.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package server_test
    12  
    13  import (
    14  	"context"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/base"
    19  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverpb"
    20  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    21  	"github.com/cockroachdb/cockroach/pkg/server/serverpb"
    22  	"github.com/cockroachdb/cockroach/pkg/testutils"
    23  	"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
    24  	"github.com/cockroachdb/cockroach/pkg/testutils/testcluster"
    25  	"github.com/cockroachdb/cockroach/pkg/util/httputil"
    26  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    27  	"github.com/cockroachdb/errors"
    28  )
    29  
    30  func TestAdminAPITableStats(t *testing.T) {
    31  	defer leaktest.AfterTest(t)()
    32  
    33  	const nodeCount = 3
    34  	tc := testcluster.StartTestCluster(t, nodeCount, base.TestClusterArgs{
    35  		ReplicationMode: base.ReplicationAuto,
    36  		ServerArgs: base.TestServerArgs{
    37  			ScanInterval:    time.Millisecond,
    38  			ScanMinIdleTime: time.Millisecond,
    39  			ScanMaxIdleTime: time.Millisecond,
    40  		},
    41  	})
    42  	defer tc.Stopper().Stop(context.Background())
    43  	server0 := tc.Server(0)
    44  
    45  	// Create clients (SQL, HTTP) connected to server 0.
    46  	db := tc.ServerConn(0)
    47  
    48  	client, err := server0.GetAdminAuthenticatedHTTPClient()
    49  	if err != nil {
    50  		t.Fatal(err)
    51  	}
    52  
    53  	client.Timeout = time.Hour // basically no timeout
    54  
    55  	// Make a single table and insert some data. The database and test have
    56  	// names which require escaping, in order to verify that database and
    57  	// table names are being handled correctly.
    58  	if _, err := db.Exec(`CREATE DATABASE "test test"`); err != nil {
    59  		t.Fatal(err)
    60  	}
    61  	if _, err := db.Exec(`
    62  		CREATE TABLE "test test"."foo foo" (
    63  			id INT PRIMARY KEY,
    64  			val STRING
    65  		)`,
    66  	); err != nil {
    67  		t.Fatal(err)
    68  	}
    69  	for i := 0; i < 10; i++ {
    70  		if _, err := db.Exec(`
    71  			INSERT INTO "test test"."foo foo" VALUES(
    72  				$1, $2
    73  			)`, i, "test",
    74  		); err != nil {
    75  			t.Fatal(err)
    76  		}
    77  	}
    78  
    79  	url := server0.AdminURL() + "/_admin/v1/databases/test test/tables/foo foo/stats"
    80  	var tsResponse serverpb.TableStatsResponse
    81  
    82  	// The new SQL table may not yet have split into its own range. Wait for
    83  	// this to occur, and for full replication.
    84  	testutils.SucceedsSoon(t, func() error {
    85  		if err := httputil.GetJSON(client, url, &tsResponse); err != nil {
    86  			t.Fatal(err)
    87  		}
    88  		if len(tsResponse.MissingNodes) != 0 {
    89  			return errors.Errorf("missing nodes: %+v", tsResponse.MissingNodes)
    90  		}
    91  		if tsResponse.RangeCount != 1 {
    92  			return errors.Errorf("Table range not yet separated.")
    93  		}
    94  		if tsResponse.NodeCount != nodeCount {
    95  			return errors.Errorf("Table range not yet replicated to %d nodes.", 3)
    96  		}
    97  		if a, e := tsResponse.ReplicaCount, int64(nodeCount); a != e {
    98  			return errors.Errorf("expected %d replicas, found %d", e, a)
    99  		}
   100  		if a, e := tsResponse.Stats.KeyCount, int64(30); a < e {
   101  			return errors.Errorf("expected at least %d total keys, found %d", e, a)
   102  		}
   103  		return nil
   104  	})
   105  
   106  	if len(tsResponse.MissingNodes) > 0 {
   107  		t.Fatalf("expected no missing nodes, found %v", tsResponse.MissingNodes)
   108  	}
   109  
   110  	// Kill a node, ensure it shows up in MissingNodes and that ReplicaCount is
   111  	// lower.
   112  	tc.StopServer(1)
   113  
   114  	if err := httputil.GetJSON(client, url, &tsResponse); err != nil {
   115  		t.Fatal(err)
   116  	}
   117  	if a, e := tsResponse.NodeCount, int64(nodeCount); a != e {
   118  		t.Errorf("expected %d nodes, found %d", e, a)
   119  	}
   120  	if a, e := tsResponse.RangeCount, int64(1); a != e {
   121  		t.Errorf("expected %d ranges, found %d", e, a)
   122  	}
   123  	if a, e := tsResponse.ReplicaCount, int64((nodeCount/2)+1); a != e {
   124  		t.Errorf("expected %d replicas, found %d", e, a)
   125  	}
   126  	if a, e := tsResponse.Stats.KeyCount, int64(10); a < e {
   127  		t.Errorf("expected at least 10 total keys, found %d", a)
   128  	}
   129  	if len(tsResponse.MissingNodes) != 1 {
   130  		t.Errorf("expected one missing node, found %v", tsResponse.MissingNodes)
   131  	}
   132  
   133  	// Call TableStats with a very low timeout. This tests that fan-out queries
   134  	// do not leak goroutines if the calling context is abandoned.
   135  	// Interestingly, the call can actually sometimes succeed, despite the small
   136  	// timeout; however, in aggregate (or in stress tests) this will suffice for
   137  	// detecting leaks.
   138  	client.Timeout = 1 * time.Nanosecond
   139  	_ = httputil.GetJSON(client, url, &tsResponse)
   140  }
   141  
   142  func TestLivenessAPI(t *testing.T) {
   143  	defer leaktest.AfterTest(t)()
   144  	tc := testcluster.StartTestCluster(t, 3, base.TestClusterArgs{})
   145  	defer tc.Stopper().Stop(context.Background())
   146  
   147  	startTime := tc.Server(0).Clock().PhysicalNow()
   148  
   149  	// We need to retry because the gossiping of liveness status is an
   150  	// asynchronous process.
   151  	testutils.SucceedsSoon(t, func() error {
   152  		var resp serverpb.LivenessResponse
   153  		if err := serverutils.GetJSONProto(tc.Server(0), "/_admin/v1/liveness", &resp); err != nil {
   154  			return err
   155  		}
   156  		if a, e := len(resp.Livenesses), tc.NumServers(); a != e {
   157  			return errors.Errorf("found %d liveness records, wanted %d", a, e)
   158  		}
   159  		livenessMap := make(map[roachpb.NodeID]kvserverpb.Liveness)
   160  		for _, l := range resp.Livenesses {
   161  			livenessMap[l.NodeID] = l
   162  		}
   163  		for i := 0; i < tc.NumServers(); i++ {
   164  			s := tc.Server(i)
   165  			sl, ok := livenessMap[s.NodeID()]
   166  			if !ok {
   167  				return errors.Errorf("found no liveness record for node %d", s.NodeID())
   168  			}
   169  			if sl.Expiration.WallTime < startTime {
   170  				return errors.Errorf(
   171  					"expected node %d liveness to expire in future (after %d), expiration was %d",
   172  					s.NodeID(),
   173  					startTime,
   174  					sl.Expiration,
   175  				)
   176  			}
   177  			status, ok := resp.Statuses[s.NodeID()]
   178  			if !ok {
   179  				return errors.Errorf("found no liveness status for node %d", s.NodeID())
   180  			}
   181  			if a, e := status, kvserverpb.NodeLivenessStatus_LIVE; a != e {
   182  				return errors.Errorf(
   183  					"liveness status for node %s was %s, wanted %s", s.NodeID(), a, e,
   184  				)
   185  			}
   186  		}
   187  		return nil
   188  	})
   189  }