github.com/bartle-stripe/trillian@v1.2.1/integration/admin/admin_integration_test.go (about)

     1  // Copyright 2017 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package admin
    16  
    17  import (
    18  	"context"
    19  	"net"
    20  	"sort"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/golang/protobuf/proto"
    25  	"github.com/golang/protobuf/ptypes"
    26  	"github.com/google/trillian"
    27  	"github.com/google/trillian/server/interceptor"
    28  	"github.com/google/trillian/storage"
    29  	"github.com/google/trillian/storage/testdb"
    30  	"github.com/google/trillian/storage/testonly"
    31  	"github.com/google/trillian/testonly/integration"
    32  	"github.com/kylelemons/godebug/pretty"
    33  	"google.golang.org/genproto/protobuf/field_mask"
    34  	"google.golang.org/grpc"
    35  	"google.golang.org/grpc/codes"
    36  	"google.golang.org/grpc/status"
    37  
    38  	sa "github.com/google/trillian/server/admin"
    39  	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
    40  )
    41  
    42  func TestAdminServer_CreateTree(t *testing.T) {
    43  	ctx := context.Background()
    44  
    45  	ts, err := setupAdminServer(ctx, t)
    46  	if err != nil {
    47  		t.Fatalf("setupAdminServer() failed: %v", err)
    48  	}
    49  	defer ts.closeAll()
    50  
    51  	invalidTree := proto.Clone(testonly.LogTree).(*trillian.Tree)
    52  	invalidTree.TreeState = trillian.TreeState_UNKNOWN_TREE_STATE
    53  
    54  	timestamp, err := ptypes.TimestampProto(time.Unix(1000, 0))
    55  	if err != nil {
    56  		t.Fatalf("TimestampProto() returned err = %v", err)
    57  	}
    58  
    59  	// All fields set below are ignored / overwritten by storage
    60  	generatedFieldsTree := proto.Clone(testonly.LogTree).(*trillian.Tree)
    61  	generatedFieldsTree.TreeId = 10
    62  	generatedFieldsTree.CreateTime = timestamp
    63  	generatedFieldsTree.UpdateTime = timestamp
    64  	generatedFieldsTree.UpdateTime.Seconds++
    65  	generatedFieldsTree.Deleted = true
    66  	generatedFieldsTree.DeleteTime = timestamp
    67  	generatedFieldsTree.DeleteTime.Seconds++
    68  
    69  	tests := []struct {
    70  		desc     string
    71  		req      *trillian.CreateTreeRequest
    72  		wantCode codes.Code
    73  	}{
    74  		{
    75  			desc: "validTree",
    76  			req:  &trillian.CreateTreeRequest{Tree: testonly.LogTree},
    77  		},
    78  		{
    79  			desc: "generatedFieldsTree",
    80  			req:  &trillian.CreateTreeRequest{Tree: generatedFieldsTree},
    81  		},
    82  		{
    83  			desc:     "nilTree",
    84  			req:      &trillian.CreateTreeRequest{},
    85  			wantCode: codes.InvalidArgument,
    86  		},
    87  		{
    88  			desc:     "invalidTree",
    89  			req:      &trillian.CreateTreeRequest{Tree: invalidTree},
    90  			wantCode: codes.InvalidArgument,
    91  		},
    92  	}
    93  
    94  	for _, test := range tests {
    95  		createdTree, err := ts.adminClient.CreateTree(ctx, test.req)
    96  		if s, ok := status.FromError(err); !ok || s.Code() != test.wantCode {
    97  			t.Errorf("%v: CreateTree() = (_, %v), wantCode = %v", test.desc, err, test.wantCode)
    98  			continue
    99  		} else if err != nil {
   100  			continue
   101  		}
   102  
   103  		// Sanity check a few generated fields
   104  		if createdTree.TreeId == 0 {
   105  			t.Errorf("%v: createdTree.TreeId = 0", test.desc)
   106  		}
   107  		if createdTree.CreateTime == nil {
   108  			t.Errorf("%v: createdTree.CreateTime = nil", test.desc)
   109  		}
   110  		if !proto.Equal(createdTree.CreateTime, createdTree.UpdateTime) {
   111  			t.Errorf("%v: createdTree.UpdateTime = %+v, want = %+v", test.desc, createdTree.UpdateTime, createdTree.CreateTime)
   112  		}
   113  		if createdTree.Deleted {
   114  			t.Errorf("%v: createdTree.Deleted = true", test.desc)
   115  		}
   116  		if createdTree.DeleteTime != nil {
   117  			t.Errorf("%v: createdTree.DeleteTime is non-nil", test.desc)
   118  		}
   119  
   120  		storedTree, err := ts.adminClient.GetTree(ctx, &trillian.GetTreeRequest{TreeId: createdTree.TreeId})
   121  		if err != nil {
   122  			t.Errorf("%v: GetTree() = (_, %v), want = (_, nil)", test.desc, err)
   123  			continue
   124  		}
   125  		if diff := pretty.Compare(storedTree, createdTree); diff != "" {
   126  			t.Errorf("%v: post-CreateTree diff (-stored +created):\n%v", test.desc, diff)
   127  		}
   128  	}
   129  }
   130  
   131  func TestAdminServer_UpdateTree(t *testing.T) {
   132  	ctx := context.Background()
   133  
   134  	ts, err := setupAdminServer(ctx, t)
   135  	if err != nil {
   136  		t.Fatalf("setupAdminServer() failed: %v", err)
   137  	}
   138  	defer ts.closeAll()
   139  
   140  	baseTree := *testonly.LogTree
   141  
   142  	// successTree specifies changes in all rw fields
   143  	successTree := &trillian.Tree{
   144  		TreeState:   trillian.TreeState_FROZEN,
   145  		DisplayName: "Brand New Tree Name",
   146  		Description: "Brand New Tree Desc",
   147  	}
   148  	successMask := &field_mask.FieldMask{Paths: []string{"tree_state", "display_name", "description"}}
   149  
   150  	successWant := baseTree
   151  	successWant.TreeState = successTree.TreeState
   152  	successWant.DisplayName = successTree.DisplayName
   153  	successWant.Description = successTree.Description
   154  	successWant.PrivateKey = nil // redacted on responses
   155  
   156  	tests := []struct {
   157  		desc                 string
   158  		createTree, wantTree *trillian.Tree
   159  		req                  *trillian.UpdateTreeRequest
   160  		wantCode             codes.Code
   161  	}{
   162  		{
   163  			desc:       "success",
   164  			createTree: &baseTree,
   165  			wantTree:   &successWant,
   166  			req:        &trillian.UpdateTreeRequest{Tree: successTree, UpdateMask: successMask},
   167  		},
   168  		{
   169  			desc: "notFound",
   170  			req: &trillian.UpdateTreeRequest{
   171  				Tree:       &trillian.Tree{TreeId: 12345, DisplayName: "New Name"},
   172  				UpdateMask: &field_mask.FieldMask{Paths: []string{"display_name"}},
   173  			},
   174  			wantCode: codes.NotFound,
   175  		},
   176  		{
   177  			desc:       "readonlyField",
   178  			createTree: &baseTree,
   179  			req: &trillian.UpdateTreeRequest{
   180  				Tree:       successTree,
   181  				UpdateMask: &field_mask.FieldMask{Paths: []string{"tree_type"}},
   182  			},
   183  			wantCode: codes.InvalidArgument,
   184  		},
   185  		{
   186  			desc:       "invalidUpdate",
   187  			createTree: &baseTree,
   188  			req: &trillian.UpdateTreeRequest{
   189  				Tree:       &trillian.Tree{}, // tree_state = UNKNOWN_TREE_STATE
   190  				UpdateMask: &field_mask.FieldMask{Paths: []string{"tree_state"}},
   191  			},
   192  			wantCode: codes.InvalidArgument,
   193  		},
   194  	}
   195  
   196  	for _, test := range tests {
   197  		if test.createTree != nil {
   198  			tree, err := ts.adminClient.CreateTree(ctx, &trillian.CreateTreeRequest{Tree: test.createTree})
   199  			if err != nil {
   200  				t.Errorf("%v: CreateTree() returned err = %v", test.desc, err)
   201  				continue
   202  			}
   203  			test.req.Tree.TreeId = tree.TreeId
   204  		}
   205  
   206  		tree, err := ts.adminClient.UpdateTree(ctx, test.req)
   207  		if s, ok := status.FromError(err); !ok || s.Code() != test.wantCode {
   208  			t.Errorf("%v: UpdateTree() returned err = %q, wantCode = %v", test.desc, err, test.wantCode)
   209  			continue
   210  		} else if err != nil {
   211  			continue
   212  		}
   213  
   214  		created, err := ptypes.Timestamp(tree.CreateTime)
   215  		if err != nil {
   216  			t.Errorf("%v: failed to convert timestamp: %v", test.desc, err)
   217  		}
   218  		updated, err := ptypes.Timestamp(tree.UpdateTime)
   219  		if err != nil {
   220  			t.Errorf("%v: failed to convert timestamp: %v", test.desc, err)
   221  		}
   222  		if created.After(updated) {
   223  			t.Errorf("%v: CreateTime > UpdateTime (%v > %v)", test.desc, tree.CreateTime, tree.UpdateTime)
   224  		}
   225  
   226  		// Copy storage-generated fields to the expected tree
   227  		want := *test.wantTree
   228  		want.TreeId = tree.TreeId
   229  		want.CreateTime = tree.CreateTime
   230  		want.UpdateTime = tree.UpdateTime
   231  		if !proto.Equal(tree, &want) {
   232  			diff := pretty.Compare(tree, &want)
   233  			t.Errorf("%v: post-UpdateTree diff:\n%v", test.desc, diff)
   234  		}
   235  	}
   236  }
   237  
   238  func TestAdminServer_GetTree(t *testing.T) {
   239  	ctx := context.Background()
   240  
   241  	ts, err := setupAdminServer(ctx, t)
   242  	if err != nil {
   243  		t.Fatalf("setupAdminServer() failed: %v", err)
   244  	}
   245  	defer ts.closeAll()
   246  
   247  	tests := []struct {
   248  		desc     string
   249  		treeID   int64
   250  		wantCode codes.Code
   251  	}{
   252  		{
   253  			desc:     "negativeTreeID",
   254  			treeID:   -1,
   255  			wantCode: codes.NotFound,
   256  		},
   257  		{
   258  			desc:     "notFound",
   259  			treeID:   12345,
   260  			wantCode: codes.NotFound,
   261  		},
   262  	}
   263  
   264  	for _, test := range tests {
   265  		_, err := ts.adminClient.GetTree(ctx, &trillian.GetTreeRequest{TreeId: test.treeID})
   266  		if s, ok := status.FromError(err); !ok || s.Code() != test.wantCode {
   267  			t.Errorf("%v: GetTree() = (_, %v), wantCode = %v", test.desc, err, test.wantCode)
   268  		}
   269  		// Success of GetTree is part of TestAdminServer_CreateTree, so it's not asserted here.
   270  	}
   271  }
   272  
   273  func TestAdminServer_ListTrees(t *testing.T) {
   274  	ctx := context.Background()
   275  
   276  	ts, err := setupAdminServer(ctx, t)
   277  	if err != nil {
   278  		t.Fatalf("setupAdminServer() failed: %v", err)
   279  	}
   280  	defer ts.closeAll()
   281  
   282  	tests := []struct {
   283  		desc string
   284  		// numTrees is the number of trees in storage. New trees are created as necessary
   285  		// and carried over to following tests.
   286  		numTrees int
   287  	}{
   288  		{desc: "empty"},
   289  		{desc: "oneTree", numTrees: 1},
   290  		{desc: "threeTrees", numTrees: 3},
   291  	}
   292  
   293  	createdTrees := []*trillian.Tree{}
   294  	for _, test := range tests {
   295  		if l := len(createdTrees); l > test.numTrees {
   296  			t.Fatalf("%v: numTrees = %v, but we already have %v stored trees", test.desc, test.numTrees, l)
   297  		} else if l < test.numTrees {
   298  			for i := l; i < test.numTrees; i++ {
   299  				var tree *trillian.Tree
   300  				if i%2 == 0 {
   301  					tree = testonly.LogTree
   302  				} else {
   303  					tree = testonly.MapTree
   304  				}
   305  				req := &trillian.CreateTreeRequest{Tree: tree}
   306  				resp, err := ts.adminClient.CreateTree(ctx, req)
   307  				if err != nil {
   308  					t.Fatalf("%v: CreateTree(_, %v) = (_, %q), want = (_, nil)", test.desc, req, err)
   309  				}
   310  				createdTrees = append(createdTrees, resp)
   311  			}
   312  			sortByTreeID(createdTrees)
   313  		}
   314  
   315  		resp, err := ts.adminClient.ListTrees(ctx, &trillian.ListTreesRequest{})
   316  		if err != nil {
   317  			t.Errorf("%v: ListTrees() = (_, %q), want = (_, nil)", test.desc, err)
   318  			continue
   319  		}
   320  
   321  		got := resp.Tree
   322  		sortByTreeID(got)
   323  		if diff := pretty.Compare(got, createdTrees); diff != "" {
   324  			t.Errorf("%v: post-ListTrees diff:\n%v", test.desc, diff)
   325  		}
   326  
   327  		for _, tree := range resp.Tree {
   328  			if tree.PrivateKey != nil {
   329  				t.Errorf("%v: PrivateKey not redacted: %v", test.desc, tree)
   330  			}
   331  		}
   332  	}
   333  }
   334  
   335  func sortByTreeID(s []*trillian.Tree) {
   336  	less := func(i, j int) bool {
   337  		return s[i].TreeId < s[j].TreeId
   338  	}
   339  	sort.Slice(s, less)
   340  }
   341  
   342  func TestAdminServer_DeleteTree(t *testing.T) {
   343  	ctx := context.Background()
   344  
   345  	ts, err := setupAdminServer(ctx, t)
   346  	if err != nil {
   347  		t.Fatalf("setupAdminServer() failed: %v", err)
   348  	}
   349  	defer ts.closeAll()
   350  
   351  	tests := []struct {
   352  		desc     string
   353  		baseTree *trillian.Tree
   354  	}{
   355  		{desc: "logTree", baseTree: testonly.LogTree},
   356  		{desc: "mapTree", baseTree: testonly.MapTree},
   357  	}
   358  
   359  	for _, test := range tests {
   360  		createdTree, err := ts.adminClient.CreateTree(ctx, &trillian.CreateTreeRequest{Tree: test.baseTree})
   361  		if err != nil {
   362  			t.Fatalf("%v: CreateTree() returned err = %v", test.desc, err)
   363  		}
   364  
   365  		deletedTree, err := ts.adminClient.DeleteTree(ctx, &trillian.DeleteTreeRequest{TreeId: createdTree.TreeId})
   366  		if err != nil {
   367  			t.Errorf("%v: DeleteTree() returned err = %v", test.desc, err)
   368  			continue
   369  		}
   370  		if deletedTree.DeleteTime == nil {
   371  			t.Errorf("%v: tree.DeleteTime = nil, want non-nil", test.desc)
   372  		}
   373  
   374  		want := proto.Clone(createdTree).(*trillian.Tree)
   375  		want.Deleted = true
   376  		want.DeleteTime = deletedTree.DeleteTime
   377  		if got := deletedTree; !proto.Equal(got, want) {
   378  			diff := pretty.Compare(got, want)
   379  			t.Errorf("%v: post-DeleteTree() diff (-got +want):\n%v", test.desc, diff)
   380  		}
   381  
   382  		storedTree, err := ts.adminClient.GetTree(ctx, &trillian.GetTreeRequest{TreeId: deletedTree.TreeId})
   383  		if err != nil {
   384  			t.Fatalf("%v: GetTree() returned err = %v", test.desc, err)
   385  		}
   386  		if got, want := storedTree, deletedTree; !proto.Equal(got, want) {
   387  			diff := pretty.Compare(got, want)
   388  			t.Errorf("%v: post-GetTree() diff (-got +want):\n%v", test.desc, diff)
   389  		}
   390  	}
   391  }
   392  
   393  func TestAdminServer_DeleteTreeErrors(t *testing.T) {
   394  	ctx := context.Background()
   395  
   396  	ts, err := setupAdminServer(ctx, t)
   397  	if err != nil {
   398  		t.Fatalf("setupAdminServer() failed: %v", err)
   399  	}
   400  	defer ts.closeAll()
   401  
   402  	createdTree, err := ts.adminClient.CreateTree(ctx, &trillian.CreateTreeRequest{Tree: testonly.LogTree})
   403  	if err != nil {
   404  		t.Fatalf("CreateTree() returned err = %v", err)
   405  	}
   406  	deletedTree, err := ts.adminClient.DeleteTree(ctx, &trillian.DeleteTreeRequest{TreeId: createdTree.TreeId})
   407  	if err != nil {
   408  		t.Fatalf("DeleteTree() returned err = %v", err)
   409  	}
   410  
   411  	tests := []struct {
   412  		desc     string
   413  		req      *trillian.DeleteTreeRequest
   414  		wantCode codes.Code
   415  	}{
   416  		{
   417  			desc:     "unknownTree",
   418  			req:      &trillian.DeleteTreeRequest{TreeId: 12345},
   419  			wantCode: codes.NotFound,
   420  		},
   421  		{
   422  			desc:     "alreadyDeleted",
   423  			req:      &trillian.DeleteTreeRequest{TreeId: deletedTree.TreeId},
   424  			wantCode: codes.FailedPrecondition,
   425  		},
   426  	}
   427  
   428  	for _, test := range tests {
   429  		_, err := ts.adminClient.DeleteTree(ctx, test.req)
   430  		if s, ok := status.FromError(err); !ok || s.Code() != test.wantCode {
   431  			t.Errorf("%v: DeleteTree() returned err = %v, wantCode = %s", test.desc, err, test.wantCode)
   432  		}
   433  	}
   434  }
   435  
   436  func TestAdminServer_UndeleteTree(t *testing.T) {
   437  	ctx := context.Background()
   438  
   439  	ts, err := setupAdminServer(ctx, t)
   440  	if err != nil {
   441  		t.Fatalf("setupAdminServer() failed: %v", err)
   442  	}
   443  	defer ts.closeAll()
   444  
   445  	tests := []struct {
   446  		desc     string
   447  		baseTree *trillian.Tree
   448  	}{
   449  		{desc: "logTree", baseTree: testonly.LogTree},
   450  		{desc: "mapTree", baseTree: testonly.MapTree},
   451  	}
   452  
   453  	for _, test := range tests {
   454  		createdTree, err := ts.adminClient.CreateTree(ctx, &trillian.CreateTreeRequest{Tree: test.baseTree})
   455  		if err != nil {
   456  			t.Fatalf("%v: CreateTree() returned err = %v", test.desc, err)
   457  		}
   458  		deletedTree, err := ts.adminClient.DeleteTree(ctx, &trillian.DeleteTreeRequest{TreeId: createdTree.TreeId})
   459  		if err != nil {
   460  			t.Fatalf("%v: DeleteTree() returned err = %v", test.desc, err)
   461  		}
   462  
   463  		undeletedTree, err := ts.adminClient.UndeleteTree(ctx, &trillian.UndeleteTreeRequest{TreeId: deletedTree.TreeId})
   464  		if err != nil {
   465  			t.Errorf("%v: UndeleteTree() returned err = %v", test.desc, err)
   466  			continue
   467  		}
   468  		if got, want := undeletedTree, createdTree; !proto.Equal(got, want) {
   469  			diff := pretty.Compare(got, want)
   470  			t.Errorf("%v: post-UndeleteTree() diff (-got +want):\n%v", test.desc, diff)
   471  		}
   472  
   473  		storedTree, err := ts.adminClient.GetTree(ctx, &trillian.GetTreeRequest{TreeId: deletedTree.TreeId})
   474  		if err != nil {
   475  			t.Fatalf("%v: GetTree() returned err = %v", test.desc, err)
   476  		}
   477  		if got, want := storedTree, createdTree; !proto.Equal(got, want) {
   478  			diff := pretty.Compare(got, want)
   479  			t.Errorf("%v: post-GetTree() diff (-got +want):\n%v", test.desc, diff)
   480  		}
   481  	}
   482  }
   483  
   484  func TestAdminServer_UndeleteTreeErrors(t *testing.T) {
   485  	ctx := context.Background()
   486  
   487  	ts, err := setupAdminServer(ctx, t)
   488  	if err != nil {
   489  		t.Fatalf("setupAdminServer() failed: %v", err)
   490  	}
   491  	defer ts.closeAll()
   492  
   493  	tree, err := ts.adminClient.CreateTree(ctx, &trillian.CreateTreeRequest{Tree: testonly.LogTree})
   494  	if err != nil {
   495  		t.Fatalf("CreateTree() returned err = %v", err)
   496  	}
   497  
   498  	tests := []struct {
   499  		desc     string
   500  		req      *trillian.UndeleteTreeRequest
   501  		wantCode codes.Code
   502  	}{
   503  		{
   504  			desc:     "unknownTree",
   505  			req:      &trillian.UndeleteTreeRequest{TreeId: 12345},
   506  			wantCode: codes.NotFound,
   507  		},
   508  		{
   509  			desc:     "notDeleted",
   510  			req:      &trillian.UndeleteTreeRequest{TreeId: tree.TreeId},
   511  			wantCode: codes.FailedPrecondition,
   512  		},
   513  	}
   514  
   515  	for _, test := range tests {
   516  		_, err := ts.adminClient.UndeleteTree(ctx, test.req)
   517  		if s, ok := status.FromError(err); !ok || s.Code() != test.wantCode {
   518  			t.Errorf("%v: UndeleteTree() returned err = %v, wantCode = %s", test.desc, err, test.wantCode)
   519  		}
   520  	}
   521  }
   522  
   523  func TestAdminServer_TreeGC(t *testing.T) {
   524  	ctx := context.Background()
   525  
   526  	ts, err := setupAdminServer(ctx, t)
   527  	if err != nil {
   528  		t.Fatalf("setupAdminServer() failed: %v", err)
   529  	}
   530  	defer ts.closeAll()
   531  
   532  	tree, err := ts.adminClient.CreateTree(ctx, &trillian.CreateTreeRequest{Tree: testonly.LogTree})
   533  	if err != nil {
   534  		t.Fatalf("CreateTree() returned err = %v", err)
   535  	}
   536  	if _, err := ts.adminClient.DeleteTree(ctx, &trillian.DeleteTreeRequest{TreeId: tree.TreeId}); err != nil {
   537  		t.Fatalf("DeleteTree() returned err = %v", err)
   538  	}
   539  
   540  	treeGC := sa.NewDeletedTreeGC(
   541  		ts.adminStorage, 1*time.Second /* threshold */, 1*time.Second /* minRunInterval */, nil /* mf */)
   542  	success := false
   543  	const attempts = 3
   544  	for i := 0; i < attempts; i++ {
   545  		treeGC.RunOnce(ctx)
   546  		_, err := ts.adminClient.GetTree(ctx, &trillian.GetTreeRequest{TreeId: tree.TreeId})
   547  		if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
   548  			success = true
   549  			break
   550  		}
   551  		time.Sleep(1 * time.Second)
   552  	}
   553  	if !success {
   554  		t.Errorf("Tree %v not hard-deleted after max attempts", tree.TreeId)
   555  	}
   556  }
   557  
   558  type testServer struct {
   559  	adminClient  trillian.TrillianAdminClient
   560  	adminStorage storage.AdminStorage
   561  
   562  	lis    net.Listener
   563  	server *grpc.Server
   564  	conn   *grpc.ClientConn
   565  }
   566  
   567  func (ts *testServer) closeAll() {
   568  	if ts.conn != nil {
   569  		ts.conn.Close()
   570  	}
   571  	if ts.server != nil {
   572  		ts.server.GracefulStop()
   573  	}
   574  	if ts.lis != nil {
   575  		ts.lis.Close()
   576  	}
   577  }
   578  
   579  // setupAdminServer prepares and starts an Admin Server, returning a testServer object.
   580  // If the returned error is nil, the callers must "defer ts.closeAll()" to avoid resource leakage.
   581  func setupAdminServer(ctx context.Context, t *testing.T) (*testServer, error) {
   582  	t.Helper()
   583  	testdb.SkipIfNoMySQL(t)
   584  	ts := &testServer{}
   585  
   586  	var err error
   587  	ts.lis, err = net.Listen("tcp", "127.0.0.1:0")
   588  	if err != nil {
   589  		return nil, err
   590  	}
   591  
   592  	registry, err := integration.NewRegistryForTests(ctx)
   593  	if err != nil {
   594  		ts.closeAll()
   595  		return nil, err
   596  	}
   597  	ts.adminStorage = registry.AdminStorage
   598  
   599  	ti := interceptor.New(
   600  		registry.AdminStorage, registry.QuotaManager, false /* quotaDryRun */, registry.MetricFactory)
   601  	ts.server = grpc.NewServer(
   602  		grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
   603  			interceptor.ErrorWrapper,
   604  			ti.UnaryInterceptor,
   605  		)),
   606  	)
   607  	trillian.RegisterTrillianAdminServer(ts.server, sa.New(registry, nil /* allowedTreeTypes */))
   608  	go ts.server.Serve(ts.lis)
   609  
   610  	ts.conn, err = grpc.Dial(ts.lis.Addr().String(), grpc.WithInsecure())
   611  	if err != nil {
   612  		ts.closeAll()
   613  		return nil, err
   614  	}
   615  	ts.adminClient = trillian.NewTrillianAdminClient(ts.conn)
   616  
   617  	return ts, nil
   618  }