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 }