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 }