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