vitess.io/vitess@v0.16.2/go/vt/vtctl/grpcvtctldserver/testutil/util.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package testutil contains utility functions for writing tests for the 18 // grpcvtctldserver. 19 package testutil 20 21 import ( 22 "context" 23 "errors" 24 "fmt" 25 "testing" 26 27 "google.golang.org/protobuf/proto" 28 29 "github.com/stretchr/testify/require" 30 "golang.org/x/net/nettest" 31 "google.golang.org/grpc" 32 33 "vitess.io/vitess/go/vt/topo" 34 "vitess.io/vitess/go/vt/topo/topoproto" 35 "vitess.io/vitess/go/vt/vtctl/vtctldclient" 36 37 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 38 vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata" 39 vtctlservicepb "vitess.io/vitess/go/vt/proto/vtctlservice" 40 ) 41 42 // WithTestServer creates a gRPC server listening locally with the given RPC 43 // implementation, then runs the test func with a client created to point at 44 // that server. 45 func WithTestServer( 46 t *testing.T, 47 server vtctlservicepb.VtctldServer, 48 test func(t *testing.T, client vtctldclient.VtctldClient), 49 ) { 50 lis, err := nettest.NewLocalListener("tcp") 51 require.NoError(t, err, "cannot create local listener") 52 53 defer lis.Close() 54 55 s := grpc.NewServer() 56 vtctlservicepb.RegisterVtctldServer(s, server) 57 58 go s.Serve(lis) 59 defer s.Stop() 60 61 client, err := vtctldclient.New("grpc", lis.Addr().String()) 62 require.NoError(t, err, "cannot create vtctld client") 63 defer client.Close() 64 65 test(t, client) 66 } 67 68 // WithTestServers creates N gRPC servers listening locally with the given RPC 69 // implementations, and then runs the test func with N clients created, where 70 // clients[i] points at servers[i]. 71 func WithTestServers( 72 t *testing.T, 73 test func(t *testing.T, clients ...vtctldclient.VtctldClient), 74 servers ...vtctlservicepb.VtctldServer, 75 ) { 76 // Declare our recursive helper function so it can refer to itself. 77 var withTestServers func(t *testing.T, servers ...vtctlservicepb.VtctldServer) 78 79 // Preallocate a slice of clients we're eventually going to call the test 80 // function with. 81 clients := make([]vtctldclient.VtctldClient, 0, len(servers)) 82 83 withTestServers = func(t *testing.T, servers ...vtctlservicepb.VtctldServer) { 84 if len(servers) == 0 { 85 // We've started up all the test servers and accumulated clients for 86 // each of them (or there were no test servers to start, and we've 87 // accumulated no clients), so finally we run the test and stop 88 // recursing. 89 test(t, clients...) 90 91 return 92 } 93 94 // Start up a test server for the head of our server slice, accumulate 95 // the resulting client, and recurse on the tail of our server slice. 96 WithTestServer(t, servers[0], func(t *testing.T, client vtctldclient.VtctldClient) { 97 clients = append(clients, client) 98 withTestServers(t, servers[1:]...) 99 }) 100 } 101 102 withTestServers(t, servers...) 103 } 104 105 // AddKeyspace adds a keyspace to a topology, failing a test if that keyspace 106 // could not be added. It shallow copies the proto struct to prevent XXX_ fields 107 // from changing in the marshalling. 108 func AddKeyspace(ctx context.Context, t *testing.T, ts *topo.Server, ks *vtctldatapb.Keyspace) { 109 err := ts.CreateKeyspace(ctx, ks.Name, proto.Clone(ks.Keyspace).(*topodatapb.Keyspace)) 110 require.NoError(t, err) 111 } 112 113 // AddKeyspaces adds a list of keyspaces to the topology, failing a test if any 114 // of those keyspaces cannot be added. See AddKeyspace for details. 115 func AddKeyspaces(ctx context.Context, t *testing.T, ts *topo.Server, keyspaces ...*vtctldatapb.Keyspace) { 116 for _, keyspace := range keyspaces { 117 AddKeyspace(ctx, t, ts, keyspace) 118 } 119 } 120 121 // AddTabletOptions is a container for different behaviors tests need from 122 // AddTablet. 123 type AddTabletOptions struct { 124 // AlsoSetShardPrimary is an option to control additional setup to take when 125 // AddTablet receives a tablet of type PRIMARY. When set, AddTablet will also 126 // update the shard record to make that tablet the primary, and fail the 127 // test if the shard record has a serving primary already. 128 AlsoSetShardPrimary bool 129 // ForceSetShardPrimary, when combined with AlsoSetShardPrimary, will ignore 130 // any existing primary in the shard, making the current tablet the serving 131 // primary (given it is type PRIMARY), and log that it has done so. 132 ForceSetShardPrimary bool 133 // SkipShardCreation, when set, makes AddTablet never attempt to create a 134 // shard record in the topo under any circumstances. 135 SkipShardCreation bool 136 } 137 138 // AddTablet adds a tablet to the topology, failing a test if that tablet record 139 // could not be created. It shallow copies to prevent XXX_ fields from changing, 140 // including nested proto message fields. 141 // 142 // AddTablet also optionally adds empty keyspace and shard records to the 143 // topology, if they are set on the tablet record and they cannot be retrieved 144 // from the topo server without error. 145 // 146 // If AddTablet receives a tablet record with a keyspace and shard set, and that 147 // tablet's type is PRIMARY, and opts.AlsoSetShardPrimary is set, then AddTablet 148 // will update the shard record to make that tablet the shard primary and set the 149 // shard to serving. If that shard record already has a serving primary, then 150 // AddTablet will fail the test. 151 func AddTablet(ctx context.Context, t *testing.T, ts *topo.Server, tablet *topodatapb.Tablet, opts *AddTabletOptions) { 152 tablet = proto.Clone(tablet).(*topodatapb.Tablet) 153 if opts == nil { 154 opts = &AddTabletOptions{} 155 } 156 157 err := ts.CreateTablet(ctx, tablet) 158 require.NoError(t, err, "CreateTablet(%+v)", tablet) 159 160 if opts.SkipShardCreation { 161 return 162 } 163 164 if tablet.Keyspace != "" { 165 if _, err := ts.GetKeyspace(ctx, tablet.Keyspace); err != nil { 166 err := ts.CreateKeyspace(ctx, tablet.Keyspace, &topodatapb.Keyspace{}) 167 require.NoError(t, err, "CreateKeyspace(%s)", tablet.Keyspace) 168 } 169 170 if tablet.Shard != "" { 171 if _, err := ts.GetShard(ctx, tablet.Keyspace, tablet.Shard); err != nil { 172 err := ts.CreateShard(ctx, tablet.Keyspace, tablet.Shard) 173 require.NoError(t, err, "CreateShard(%s, %s)", tablet.Keyspace, tablet.Shard) 174 } 175 176 if tablet.Type == topodatapb.TabletType_PRIMARY && opts.AlsoSetShardPrimary { 177 _, err := ts.UpdateShardFields(ctx, tablet.Keyspace, tablet.Shard, func(si *topo.ShardInfo) error { 178 if si.IsPrimaryServing && si.PrimaryAlias != nil { 179 msg := fmt.Sprintf("shard %v/%v already has a serving primary (%v)", tablet.Keyspace, tablet.Shard, topoproto.TabletAliasString(si.PrimaryAlias)) 180 181 if !opts.ForceSetShardPrimary { 182 return errors.New(msg) 183 } 184 185 t.Logf("%s; replacing with %v because ForceSetShardPrimary = true", msg, topoproto.TabletAliasString(tablet.Alias)) 186 } 187 188 si.PrimaryAlias = tablet.Alias 189 si.IsPrimaryServing = true 190 si.PrimaryTermStartTime = tablet.PrimaryTermStartTime 191 192 return nil 193 }) 194 require.NoError(t, err, "UpdateShardFields(%s, %s) to set %s as serving primary failed", tablet.Keyspace, tablet.Shard, topoproto.TabletAliasString(tablet.Alias)) 195 } 196 } 197 } 198 } 199 200 // AddTablets adds a list of tablets to the topology. See AddTablet for more 201 // details. 202 func AddTablets(ctx context.Context, t *testing.T, ts *topo.Server, opts *AddTabletOptions, tablets ...*topodatapb.Tablet) { 203 for _, tablet := range tablets { 204 AddTablet(ctx, t, ts, tablet, opts) 205 } 206 } 207 208 // AddShards adds a list of shards to the topology, failing a test if any of the 209 // shard records could not be created. It also ensures that every shard's 210 // keyspace exists, or creates an empty keyspace if that shard's keyspace does 211 // not exist. 212 func AddShards(ctx context.Context, t *testing.T, ts *topo.Server, shards ...*vtctldatapb.Shard) { 213 for _, shard := range shards { 214 if shard.Keyspace != "" { 215 if _, err := ts.GetKeyspace(ctx, shard.Keyspace); err != nil { 216 err := ts.CreateKeyspace(ctx, shard.Keyspace, &topodatapb.Keyspace{}) 217 require.NoError(t, err, "CreateKeyspace(%s)", shard.Keyspace) 218 } 219 } 220 221 err := ts.CreateShard(ctx, shard.Keyspace, shard.Name) 222 require.NoError(t, err, "CreateShard(%s/%s)", shard.Keyspace, shard.Name) 223 224 if shard.Shard != nil { 225 _, err := ts.UpdateShardFields(ctx, shard.Keyspace, shard.Name, func(si *topo.ShardInfo) error { 226 si.Shard = shard.Shard 227 228 return nil 229 }) 230 require.NoError(t, err, "UpdateShardFields(%s/%s, %v)", shard.Keyspace, shard.Name, shard.Shard) 231 } 232 } 233 } 234 235 // SetupReplicationGraphs creates a set of ShardReplication objects in the topo, 236 // failing the test if any of the records could not be created. 237 func SetupReplicationGraphs(ctx context.Context, t *testing.T, ts *topo.Server, replicationGraphs ...*topo.ShardReplicationInfo) { 238 for _, graph := range replicationGraphs { 239 err := ts.UpdateShardReplicationFields(ctx, graph.Cell(), graph.Keyspace(), graph.Shard(), func(sr *topodatapb.ShardReplication) error { 240 sr.Nodes = graph.Nodes 241 return nil 242 }) 243 require.NoError(t, err, "could not save replication graph for %s/%s in cell %v", graph.Keyspace(), graph.Shard(), graph.Cell()) 244 } 245 } 246 247 // UpdateSrvKeyspaces updates a set of SrvKeyspace records, grouped by cell and 248 // then by keyspace. It fails the test if any records cannot be updated. 249 func UpdateSrvKeyspaces(ctx context.Context, t *testing.T, ts *topo.Server, srvkeyspacesByCellByKeyspace map[string]map[string]*topodatapb.SrvKeyspace) { 250 for cell, srvKeyspacesByKeyspace := range srvkeyspacesByCellByKeyspace { 251 for keyspace, srvKeyspace := range srvKeyspacesByKeyspace { 252 err := ts.UpdateSrvKeyspace(ctx, cell, keyspace, srvKeyspace) 253 require.NoError(t, err, "UpdateSrvKeyspace(%v, %v, %v)", cell, keyspace, srvKeyspace) 254 } 255 } 256 } 257 258 func MockGetVersionFromTablet(addrVersionMap map[string]string) func(ta string) (string, error) { 259 return func(tabletAddr string) (string, error) { 260 return addrVersionMap[tabletAddr], nil 261 } 262 }