1 package sa
2
3 import (
4 "database/sql"
5 "fmt"
6 "time"
7
8 "github.com/go-sql-driver/mysql"
9 "github.com/letsencrypt/borp"
10 "github.com/prometheus/client_golang/prometheus"
11
12 "github.com/letsencrypt/boulder/cmd"
13 "github.com/letsencrypt/boulder/core"
14 boulderDB "github.com/letsencrypt/boulder/db"
15 blog "github.com/letsencrypt/boulder/log"
16 )
17
18
19
20
21
22 type DbSettings struct {
23
24
25
26
27
28 MaxOpenConns int
29
30
31
32
33
34 MaxIdleConns int
35
36
37
38
39 ConnMaxLifetime time.Duration
40
41
42
43
44
45 ConnMaxIdleTime time.Duration
46 }
47
48
49
50
51
52 func InitWrappedDb(config cmd.DBConfig, scope prometheus.Registerer, logger blog.Logger) (*boulderDB.WrappedMap, error) {
53 url, err := config.URL()
54 if err != nil {
55 return nil, fmt.Errorf("failed to load DBConnect URL: %s", err)
56 }
57
58 settings := DbSettings{
59 MaxOpenConns: config.MaxOpenConns,
60 MaxIdleConns: config.MaxIdleConns,
61 ConnMaxLifetime: config.ConnMaxLifetime.Duration,
62 ConnMaxIdleTime: config.ConnMaxIdleTime.Duration,
63 }
64
65 mysqlConfig, err := mysql.ParseDSN(url)
66 if err != nil {
67 return nil, err
68 }
69
70 dbMap, err := newDbMapFromMySQLConfig(mysqlConfig, settings, scope, logger)
71 if err != nil {
72 return nil, err
73 }
74
75 return dbMap, nil
76 }
77
78
79
80
81
82 func DBMapForTest(dbConnect string) (*boulderDB.WrappedMap, error) {
83 return DBMapForTestWithLog(dbConnect, nil)
84 }
85
86
87
88 func DBMapForTestWithLog(dbConnect string, log blog.Logger) (*boulderDB.WrappedMap, error) {
89 var err error
90 var config *mysql.Config
91
92 config, err = mysql.ParseDSN(dbConnect)
93 if err != nil {
94 return nil, err
95 }
96
97 return newDbMapFromMySQLConfig(config, DbSettings{}, nil, log)
98 }
99
100
101
102 var sqlOpen = func(dbType, connectStr string) (*sql.DB, error) {
103 return sql.Open(dbType, connectStr)
104 }
105
106
107 var setMaxOpenConns = func(db *sql.DB, maxOpenConns int) {
108 if maxOpenConns != 0 {
109 db.SetMaxOpenConns(maxOpenConns)
110 }
111 }
112
113
114 var setMaxIdleConns = func(db *sql.DB, maxIdleConns int) {
115 if maxIdleConns != 0 {
116 db.SetMaxIdleConns(maxIdleConns)
117 }
118 }
119
120
121 var setConnMaxLifetime = func(db *sql.DB, connMaxLifetime time.Duration) {
122 if connMaxLifetime != 0 {
123 db.SetConnMaxLifetime(connMaxLifetime)
124 }
125 }
126
127
128 var setConnMaxIdleTime = func(db *sql.DB, connMaxIdleTime time.Duration) {
129 if connMaxIdleTime != 0 {
130 db.SetConnMaxIdleTime(connMaxIdleTime)
131 }
132 }
133
134
135
136
137
138
139
140
141
142
143
144
145 func newDbMapFromMySQLConfig(config *mysql.Config, settings DbSettings, scope prometheus.Registerer, logger blog.Logger) (*boulderDB.WrappedMap, error) {
146 err := adjustMySQLConfig(config)
147 if err != nil {
148 return nil, err
149 }
150
151 db, err := sqlOpen("mysql", config.FormatDSN())
152 if err != nil {
153 return nil, err
154 }
155 if err = db.Ping(); err != nil {
156 return nil, err
157 }
158 setMaxOpenConns(db, settings.MaxOpenConns)
159 setMaxIdleConns(db, settings.MaxIdleConns)
160 setConnMaxLifetime(db, settings.ConnMaxLifetime)
161 setConnMaxIdleTime(db, settings.ConnMaxIdleTime)
162
163 if scope != nil {
164 err = initDBMetrics(db, scope, settings, config.Addr, config.User)
165 if err != nil {
166 return nil, fmt.Errorf("while initializing metrics: %w", err)
167 }
168 }
169
170 dialect := borp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"}
171 dbmap := &borp.DbMap{Db: db, Dialect: dialect, TypeConverter: BoulderTypeConverter{}}
172
173 if logger != nil {
174 dbmap.TraceOn("SQL: ", &SQLLogger{logger})
175 }
176
177 initTables(dbmap)
178 return boulderDB.NewWrappedMap(dbmap), nil
179 }
180
181
182 func adjustMySQLConfig(conf *mysql.Config) error {
183
184 conf.ParseTime = true
185
186
187
188 conf.ClientFoundRows = true
189
190 if conf.Params == nil {
191 conf.Params = make(map[string]string)
192 }
193
194
195 setDefault := func(name, value string) {
196 _, ok := conf.Params[name]
197 if !ok {
198 conf.Params[name] = value
199 }
200 }
201
202
203 omitZero := func(name string) {
204 if conf.Params[name] == "0" {
205 delete(conf.Params, name)
206 }
207 }
208
209
210
211
212
213
214
215 setDefault("sql_mode", "'STRICT_ALL_TABLES'")
216
217
218
219
220
221
222
223
224
225 if conf.ReadTimeout != 0 {
226
227
228 readTimeout := conf.ReadTimeout.Seconds()
229 setDefault("max_statement_time", fmt.Sprintf("%g", readTimeout*0.95))
230 setDefault("long_query_time", fmt.Sprintf("%g", readTimeout*0.80))
231 }
232
233 omitZero("max_statement_time")
234 omitZero("long_query_time")
235
236
237 for k, v := range conf.Params {
238 err := checkMariaDBSystemVariables(k, v)
239 if err != nil {
240 return err
241 }
242 }
243
244 return nil
245 }
246
247
248 type SQLLogger struct {
249 blog.Logger
250 }
251
252
253 func (log *SQLLogger) Printf(format string, v ...interface{}) {
254 log.Debugf(format, v...)
255 }
256
257
258
259
260
261
262
263 func initTables(dbMap *borp.DbMap) {
264 regTable := dbMap.AddTableWithName(regModel{}, "registrations").SetKeys(true, "ID")
265
266 regTable.SetVersionCol("LockCol")
267 regTable.ColMap("Key").SetNotNull(true)
268 regTable.ColMap("KeySHA256").SetNotNull(true).SetUnique(true)
269 dbMap.AddTableWithName(issuedNameModel{}, "issuedNames").SetKeys(true, "ID")
270 dbMap.AddTableWithName(core.Certificate{}, "certificates").SetKeys(true, "ID")
271 dbMap.AddTableWithName(core.CertificateStatus{}, "certificateStatus").SetKeys(true, "ID")
272 dbMap.AddTableWithName(core.FQDNSet{}, "fqdnSets").SetKeys(true, "ID")
273 dbMap.AddTableWithName(orderModel{}, "orders").SetKeys(true, "ID")
274 dbMap.AddTableWithName(orderToAuthzModel{}, "orderToAuthz").SetKeys(false, "OrderID", "AuthzID")
275 dbMap.AddTableWithName(requestedNameModel{}, "requestedNames").SetKeys(false, "OrderID")
276 dbMap.AddTableWithName(orderFQDNSet{}, "orderFqdnSets").SetKeys(true, "ID")
277 dbMap.AddTableWithName(authzModel{}, "authz2").SetKeys(true, "ID")
278 dbMap.AddTableWithName(orderToAuthzModel{}, "orderToAuthz2").SetKeys(false, "OrderID", "AuthzID")
279 dbMap.AddTableWithName(recordedSerialModel{}, "serials").SetKeys(true, "ID")
280 dbMap.AddTableWithName(precertificateModel{}, "precertificates").SetKeys(true, "ID")
281 dbMap.AddTableWithName(keyHashModel{}, "keyHashToSerial").SetKeys(true, "ID")
282 dbMap.AddTableWithName(incidentModel{}, "incidents").SetKeys(true, "ID")
283 dbMap.AddTable(incidentSerialModel{})
284 dbMap.AddTableWithName(crlShardModel{}, "crlShards").SetKeys(true, "ID")
285 dbMap.AddTableWithName(revokedCertModel{}, "revokedCertificates").SetKeys(true, "ID")
286
287
288 dbMap.AddTableWithName(CertStatusMetadata{}, "certificateStatus")
289 dbMap.AddTableWithName(crlEntryModel{}, "certificateStatus")
290 }
291
View as plain text