This repository has been archived by the owner on May 29, 2024. It is now read-only.
forked from google/trillian
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtestdb.go
292 lines (253 loc) · 8.33 KB
/
testdb.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
// Copyright 2017 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package testdb creates new databases for tests.
package testdb
import (
"bytes"
"context"
"database/sql"
"fmt"
"io/ioutil"
"log"
"net/url"
"os"
"strings"
"testing"
"time"
"github.com/google/trillian/testonly"
"golang.org/x/sys/unix"
"k8s.io/klog/v2"
_ "github.com/go-sql-driver/mysql" // mysql driver
_ "github.com/lib/pq" // postgres driver
)
const (
// MySQLURIEnv is the name of the ENV variable checked for the test MySQL
// instance URI to use. The value must have a trailing slash.
MySQLURIEnv = "TEST_MYSQL_URI"
// Note: sql.Open requires the URI to end with a slash.
defaultTestMySQLURI = "root@tcp(127.0.0.1)/"
// CockroachDBURIEnv is the name of the ENV variable checked for the test CockroachDB
// instance URI to use. The value must have a trailing slash.
CockroachDBURIEnv = "TEST_COCKROACHDB_URI"
defaultTestCockroachDBURI = "postgres://root@localhost:26257/?sslmode=disable"
)
type storageDriverInfo struct {
sqlDriverName string
schema string
uriFunc func(paths ...string) string
}
var (
trillianMySQLSchema = testonly.RelativeToPackage("../mysql/schema/storage.sql")
trillianCRDBSchema = testonly.RelativeToPackage("../crdb/schema/storage.sql")
)
// DriverName is the name of a database driver.
type DriverName string
const (
// DriverMySQL is the identifier for the MySQL storage driver.
DriverMySQL DriverName = "mysql"
// DriverCockroachDB is the identifier for the CockroachDB storage driver.
DriverCockroachDB DriverName = "cockroachdb"
)
var driverMapping = map[DriverName]storageDriverInfo{
DriverMySQL: {
sqlDriverName: "mysql",
schema: trillianMySQLSchema,
uriFunc: mysqlURI,
},
DriverCockroachDB: {
sqlDriverName: "postgres",
schema: trillianCRDBSchema,
uriFunc: crdbURI,
},
}
// mysqlURI returns the MySQL connection URI to use for tests. It returns the
// value in the ENV variable defined by MySQLURIEnv. If the value is empty,
// returns defaultTestMySQLURI.
//
// We use an ENV variable, rather than a flag, for flexibility. Only a subset
// of the tests in this repo require a database and import this package. With a
// flag, it would be necessary to distinguish "go test" invocations that need a
// database, and those that don't. ENV allows to "blanket apply" this setting.
func mysqlURI(dbRef ...string) string {
var stringurl string
if e := os.Getenv(MySQLURIEnv); len(e) > 0 {
stringurl = e
} else {
stringurl = defaultTestMySQLURI
}
for _, ref := range dbRef {
separator := "/"
if strings.HasSuffix(stringurl, "/") {
separator = ""
}
stringurl = strings.Join([]string{stringurl, ref}, separator)
}
return stringurl
}
// crdbURI returns the CockroachDB connection URI to use for tests. It returns the
// value in the ENV variable defined by CockroachDBURIEnv. If the value is empty,
// returns defaultTestCockroachDBURI.
func crdbURI(dbRef ...string) string {
var uri *url.URL
if e := os.Getenv(CockroachDBURIEnv); len(e) > 0 {
uri = getURL(e)
} else {
uri = getURL(defaultTestCockroachDBURI)
}
return addPathToURI(uri, dbRef...)
}
func addPathToURI(uri *url.URL, paths ...string) string {
if len(paths) > 0 {
for _, ref := range paths {
currentPaths := uri.Path
// If the path is the root path, we don't want to append a slash.
if currentPaths == "/" {
currentPaths = ""
}
uri.Path = strings.Join([]string{currentPaths, ref}, "/")
}
}
return uri.String()
}
func getURL(unparsedurl string) *url.URL {
//nolint:errcheck // We're not expecting an error here.
u, _ := url.Parse(unparsedurl)
return u
}
// MySQLAvailable indicates whether the configured MySQL database is available.
func MySQLAvailable() bool {
return dbAvailable(DriverMySQL)
}
// CockroachDBAvailable indicates whether the configured CockroachDB database is available.
func CockroachDBAvailable() bool {
return dbAvailable(DriverCockroachDB)
}
func dbAvailable(driver DriverName) bool {
driverName := driverMapping[driver].sqlDriverName
uri := driverMapping[driver].uriFunc()
db, err := sql.Open(driverName, uri)
if err != nil {
log.Printf("sql.Open(): %v", err)
return false
}
defer db.Close()
if err := db.Ping(); err != nil {
log.Printf("db.Ping(): %v", err)
return false
}
return true
}
// SetFDLimit sets the soft limit on the maximum number of open file descriptors.
// See http://man7.org/linux/man-pages/man2/setrlimit.2.html
func SetFDLimit(uLimit uint64) error {
var rLimit unix.Rlimit
if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil {
return err
}
if uLimit > rLimit.Max {
return fmt.Errorf("Could not set FD limit to %v. Must be less than the hard limit %v", uLimit, rLimit.Max)
}
rLimit.Cur = uLimit
return unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit)
}
// newEmptyDB creates a new, empty database.
// It returns the database handle and a clean-up function, or an error.
// The returned clean-up function should be called once the caller is finished
// using the DB, the caller should not continue to use the returned DB after
// calling this function as it may, for example, delete the underlying
// instance.
func newEmptyDB(ctx context.Context, driver DriverName) (*sql.DB, func(context.Context), error) {
if err := SetFDLimit(2048); err != nil {
return nil, nil, err
}
inf, gotinf := driverMapping[driver]
if !gotinf {
return nil, nil, fmt.Errorf("unknown driver %q", driver)
}
db, err := sql.Open(inf.sqlDriverName, inf.uriFunc())
if err != nil {
return nil, nil, err
}
// Create a randomly-named database and then connect using the new name.
name := fmt.Sprintf("trl_%v", time.Now().UnixNano())
stmt := fmt.Sprintf("CREATE DATABASE %v", name)
if _, err := db.ExecContext(ctx, stmt); err != nil {
return nil, nil, fmt.Errorf("error running statement %q: %v", stmt, err)
}
db.Close()
uri := inf.uriFunc(name)
db, err = sql.Open(inf.sqlDriverName, uri)
if err != nil {
return nil, nil, err
}
done := func(ctx context.Context) {
defer db.Close()
if _, err := db.ExecContext(ctx, fmt.Sprintf("DROP DATABASE %v", name)); err != nil {
klog.Warningf("Failed to drop test database %q: %v", name, err)
}
}
return db, done, db.Ping()
}
// NewTrillianDB creates an empty database with the Trillian schema. The database name is randomly
// generated.
// NewTrillianDB is equivalent to Default().NewTrillianDB(ctx).
func NewTrillianDB(ctx context.Context, driver DriverName) (*sql.DB, func(context.Context), error) {
db, done, err := newEmptyDB(ctx, driver)
if err != nil {
return nil, nil, err
}
schema := driverMapping[driver].schema
sqlBytes, err := ioutil.ReadFile(schema)
if err != nil {
return nil, nil, err
}
for _, stmt := range strings.Split(sanitize(string(sqlBytes)), ";") {
stmt = strings.TrimSpace(stmt)
if stmt == "" {
continue
}
if _, err := db.ExecContext(ctx, stmt); err != nil {
return nil, nil, fmt.Errorf("error running statement %q: %v", stmt, err)
}
}
return db, done, nil
}
func sanitize(script string) string {
buf := &bytes.Buffer{}
for _, line := range strings.Split(string(script), "\n") {
line = strings.TrimSpace(line)
if line == "" || line[0] == '#' || strings.Index(line, "--") == 0 {
continue // skip empty lines and comments
}
buf.WriteString(line)
buf.WriteString("\n")
}
return buf.String()
}
// SkipIfNoMySQL is a test helper that skips tests that require a local MySQL.
func SkipIfNoMySQL(t *testing.T) {
t.Helper()
if !MySQLAvailable() {
t.Skip("Skipping test as MySQL not available")
}
t.Logf("Test MySQL available at %q", mysqlURI())
}
// SkipIfNoCockroachDB is a test helper that skips tests that require a local CockroachDB.
func SkipIfNoCockroachDB(t *testing.T) {
t.Helper()
if !CockroachDBAvailable() {
t.Skip("Skipping test as CockroachDB not available")
}
t.Logf("Test CockroachDB available at %q", crdbURI())
}