github.com/bartle-stripe/trillian@v1.2.1/storage/mysql/admin_storage_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 mysql 16 17 import ( 18 "context" 19 "database/sql" 20 "fmt" 21 "testing" 22 23 "github.com/golang/protobuf/proto" 24 "github.com/golang/protobuf/ptypes" 25 "github.com/google/trillian" 26 "github.com/google/trillian/crypto/keyspb" 27 "github.com/google/trillian/storage" 28 "github.com/google/trillian/storage/testonly" 29 ) 30 31 const selectTreeControlByID = "SELECT SigningEnabled, SequencingEnabled, SequenceIntervalSeconds FROM TreeControl WHERE TreeId = ?" 32 33 func TestMysqlAdminStorage(t *testing.T) { 34 tester := &testonly.AdminStorageTester{NewAdminStorage: func() storage.AdminStorage { 35 cleanTestDB(DB) 36 return NewAdminStorage(DB) 37 }} 38 tester.RunAllTests(t) 39 } 40 41 func TestAdminTX_CreateTree_InitializesStorageStructures(t *testing.T) { 42 cleanTestDB(DB) 43 s := NewAdminStorage(DB) 44 ctx := context.Background() 45 46 tree, err := storage.CreateTree(ctx, s, testonly.LogTree) 47 if err != nil { 48 t.Fatalf("CreateTree() failed: %v", err) 49 } 50 51 // Check if TreeControl is correctly written. 52 var signingEnabled, sequencingEnabled bool 53 var sequenceIntervalSeconds int 54 if err := DB.QueryRowContext(ctx, selectTreeControlByID, tree.TreeId).Scan(&signingEnabled, &sequencingEnabled, &sequenceIntervalSeconds); err != nil { 55 t.Fatalf("Failed to read TreeControl: %v", err) 56 } 57 // We don't mind about specific values, defaults change, but let's check 58 // that important numbers are not zeroed. 59 if sequenceIntervalSeconds <= 0 { 60 t.Errorf("sequenceIntervalSeconds = %v, want > 0", sequenceIntervalSeconds) 61 } 62 } 63 64 func TestCreateTreeInvalidStates(t *testing.T) { 65 cleanTestDB(DB) 66 s := NewAdminStorage(DB) 67 ctx := context.Background() 68 69 states := []trillian.TreeState{trillian.TreeState_DRAINING, trillian.TreeState_FROZEN} 70 71 for _, state := range states { 72 inTree := proto.Clone(testonly.LogTree).(*trillian.Tree) 73 inTree.TreeState = state 74 if _, err := storage.CreateTree(ctx, s, inTree); err == nil { 75 t.Errorf("CreateTree() state: %v got: nil want: err", state) 76 } 77 } 78 } 79 80 func TestAdminTX_TreeWithNulls(t *testing.T) { 81 cleanTestDB(DB) 82 s := NewAdminStorage(DB) 83 ctx := context.Background() 84 85 // Setup: create a tree and set all nullable columns to null. 86 // Some columns have to be manually updated, as it's not possible to set 87 // some proto fields to nil. 88 tree, err := storage.CreateTree(ctx, s, testonly.LogTree) 89 if err != nil { 90 t.Fatalf("CreateTree() failed: %v", err) 91 } 92 treeID := tree.TreeId 93 94 if err := setNulls(ctx, DB, treeID); err != nil { 95 t.Fatalf("setNulls() = %v, want = nil", err) 96 } 97 98 tests := []struct { 99 desc string 100 fn storage.AdminTXFunc 101 }{ 102 { 103 desc: "GetTree", 104 fn: func(ctx context.Context, tx storage.AdminTX) error { 105 _, err := tx.GetTree(ctx, treeID) 106 return err 107 }, 108 }, 109 { 110 // ListTreeIDs *shouldn't* care about other columns, but let's test it just 111 // in case. 112 desc: "ListTreeIDs", 113 fn: func(ctx context.Context, tx storage.AdminTX) error { 114 ids, err := tx.ListTreeIDs(ctx, false /* includeDeleted */) 115 if err != nil { 116 return err 117 } 118 for _, id := range ids { 119 if id == treeID { 120 return nil 121 } 122 } 123 return fmt.Errorf("ID not found: %v", treeID) 124 }, 125 }, 126 { 127 desc: "ListTrees", 128 fn: func(ctx context.Context, tx storage.AdminTX) error { 129 trees, err := tx.ListTrees(ctx, false /* includeDeleted */) 130 if err != nil { 131 return err 132 } 133 for _, tree := range trees { 134 if tree.TreeId == treeID { 135 return nil 136 } 137 } 138 return fmt.Errorf("ID not found: %v", treeID) 139 }, 140 }, 141 } 142 for _, test := range tests { 143 if err := s.ReadWriteTransaction(ctx, test.fn); err != nil { 144 t.Errorf("%v: err = %v, want = nil", test.desc, err) 145 } 146 } 147 } 148 149 func TestAdminTX_StorageSettingsNotSupported(t *testing.T) { 150 cleanTestDB(DB) 151 s := NewAdminStorage(DB) 152 ctx := context.Background() 153 154 settings, err := ptypes.MarshalAny(&keyspb.PEMKeyFile{}) 155 if err != nil { 156 t.Fatalf("Error marshaling proto: %v", err) 157 } 158 159 tests := []struct { 160 desc string 161 // fn attempts to either create or update a tree with a non-nil, valid Any proto 162 // on Tree.StorageSettings. It's expected to return an error. 163 fn func(storage.AdminStorage) error 164 }{ 165 { 166 desc: "CreateTree", 167 fn: func(s storage.AdminStorage) error { 168 tree := *testonly.LogTree 169 tree.StorageSettings = settings 170 _, err := storage.CreateTree(ctx, s, &tree) 171 return err 172 }, 173 }, 174 { 175 desc: "UpdateTree", 176 fn: func(s storage.AdminStorage) error { 177 tree, err := storage.CreateTree(ctx, s, testonly.LogTree) 178 if err != nil { 179 t.Fatalf("CreateTree() failed with err = %v", err) 180 } 181 _, err = storage.UpdateTree(ctx, s, tree.TreeId, func(tree *trillian.Tree) { tree.StorageSettings = settings }) 182 return err 183 }, 184 }, 185 } 186 for _, test := range tests { 187 if err := test.fn(s); err == nil { 188 t.Errorf("%v: err = nil, want non-nil", test.desc) 189 } 190 } 191 } 192 193 func TestAdminTX_HardDeleteTree(t *testing.T) { 194 cleanTestDB(DB) 195 s := NewAdminStorage(DB) 196 ctx := context.Background() 197 198 tree, err := storage.CreateTree(ctx, s, testonly.LogTree) 199 if err != nil { 200 t.Fatalf("CreateTree() returned err = %v", err) 201 } 202 203 if err := s.ReadWriteTransaction(ctx, func(ctx context.Context, tx storage.AdminTX) error { 204 if _, err := tx.SoftDeleteTree(ctx, tree.TreeId); err != nil { 205 return err 206 } 207 return tx.HardDeleteTree(ctx, tree.TreeId) 208 }); err != nil { 209 t.Fatalf("ReadWriteTransaction() returned err = %v", err) 210 } 211 212 // Unlike the HardDelete tests on AdminStorageTester, here we have the chance to poke inside the 213 // database and check that the rows are gone, so let's do just that. 214 // If there's no record on Trees, then there can be no record in any of the dependent tables. 215 var name string 216 if err := DB.QueryRowContext(ctx, "SELECT DisplayName FROM Trees WHERE TreeId = ?", tree.TreeId).Scan(&name); err != sql.ErrNoRows { 217 t.Errorf("QueryRowContext() returned err = %v, want = %v", err, sql.ErrNoRows) 218 } 219 } 220 221 func TestCheckDatabaseAccessible_Fails(t *testing.T) { 222 // Pass in a closed database to provoke a failure. 223 db := openTestDBOrDie() 224 cleanTestDB(db) 225 s := NewAdminStorage(db) 226 db.Close() 227 ctx := context.Background() 228 if err := s.CheckDatabaseAccessible(ctx); err == nil { 229 t.Error("TestCheckDatabaseAccessible_Fails got: nil, want: err") 230 } 231 } 232 233 func TestCheckDatabaseAccessible_OK(t *testing.T) { 234 cleanTestDB(DB) 235 s := NewAdminStorage(DB) 236 ctx := context.Background() 237 if err := s.CheckDatabaseAccessible(ctx); err != nil { 238 t.Errorf("TestCheckDatabaseAccessible_OK got: %v, want: nil", err) 239 } 240 } 241 242 func setNulls(ctx context.Context, db *sql.DB, treeID int64) error { 243 stmt, err := db.PrepareContext(ctx, "UPDATE Trees SET DisplayName = NULL, Description = NULL WHERE TreeId = ?") 244 if err != nil { 245 return err 246 } 247 defer stmt.Close() 248 _, err = stmt.ExecContext(ctx, treeID) 249 return err 250 }