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 }