github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/postgresrunner/postgresrunner.go (about) 1 package postgresrunner 2 3 import ( 4 "database/sql" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "os/user" 10 "path/filepath" 11 "strconv" 12 "syscall" 13 14 "code.cloudfoundry.org/lager/lagertest" 15 16 "github.com/pf-qiu/concourse/v6/atc/db" 17 "github.com/pf-qiu/concourse/v6/atc/db/migration" 18 "github.com/onsi/ginkgo" 19 . "github.com/onsi/gomega" 20 "github.com/onsi/gomega/gexec" 21 "github.com/tedsuo/ifrit/ginkgomon" 22 ) 23 24 type Runner struct { 25 Port int 26 } 27 28 func (runner Runner) Run(signals <-chan os.Signal, ready chan<- struct{}) error { 29 defer ginkgo.GinkgoRecover() 30 31 pgBase := filepath.Join(os.TempDir(), "concourse-pg-runner") 32 33 err := os.MkdirAll(pgBase, 0755) 34 Expect(err).NotTo(HaveOccurred()) 35 36 tmpdir, err := ioutil.TempDir(pgBase, "postgres") 37 Expect(err).NotTo(HaveOccurred()) 38 39 currentUser, err := user.Current() 40 Expect(err).NotTo(HaveOccurred()) 41 42 initdbPath, err := exec.LookPath("initdb") 43 Expect(err).NotTo(HaveOccurred()) 44 45 postgresPath, err := exec.LookPath("postgres") 46 Expect(err).NotTo(HaveOccurred()) 47 48 initCmd := exec.Command(initdbPath, "-U", "postgres", "-D", tmpdir, "-E", "UTF8", "--no-local") 49 startCmd := exec.Command(postgresPath, "-k", "/tmp", "-D", tmpdir, "-h", "127.0.0.1", "-p", strconv.Itoa(runner.Port)) 50 51 if currentUser.Uid == "0" { 52 pgUser, err := user.Lookup("postgres") 53 Expect(err).NotTo(HaveOccurred()) 54 55 var uid, gid uint32 56 _, err = fmt.Sscanf(pgUser.Uid, "%d", &uid) 57 Expect(err).NotTo(HaveOccurred()) 58 59 _, err = fmt.Sscanf(pgUser.Gid, "%d", &gid) 60 Expect(err).NotTo(HaveOccurred()) 61 62 err = os.Chown(tmpdir, int(uid), int(gid)) 63 Expect(err).NotTo(HaveOccurred()) 64 65 credential := &syscall.Credential{Uid: uid, Gid: gid} 66 67 initCmd.SysProcAttr = &syscall.SysProcAttr{} 68 initCmd.SysProcAttr.Credential = credential 69 70 startCmd.SysProcAttr = &syscall.SysProcAttr{} 71 startCmd.SysProcAttr.Credential = credential 72 } 73 74 session, err := gexec.Start( 75 initCmd, 76 gexec.NewPrefixedWriter("[o][initdb] ", ginkgo.GinkgoWriter), 77 gexec.NewPrefixedWriter("[e][initdb] ", ginkgo.GinkgoWriter), 78 ) 79 Expect(err).NotTo(HaveOccurred()) 80 81 <-session.Exited 82 83 Expect(session).To(gexec.Exit(0)) 84 85 ginkgoRunner := &ginkgomon.Runner{ 86 Name: "postgres", 87 Command: startCmd, 88 AnsiColorCode: "90m", 89 StartCheck: "database system is ready to accept connections", 90 Cleanup: func() { 91 os.RemoveAll(tmpdir) 92 }, 93 } 94 95 return ginkgoRunner.Run(signals, ready) 96 } 97 98 func (runner *Runner) MigrateToVersion(version int) { 99 err := migration.NewOpenHelper( 100 "postgres", 101 runner.DataSourceName(), 102 nil, 103 nil, 104 nil, 105 ).MigrateToVersion(version) 106 Expect(err).NotTo(HaveOccurred()) 107 } 108 109 func (runner *Runner) TryOpenDBAtVersion(version int) (*sql.DB, error) { 110 dbConn, err := migration.NewOpenHelper( 111 "postgres", 112 runner.DataSourceName(), 113 nil, 114 nil, 115 nil, 116 ).OpenAtVersion(version) 117 118 if err != nil { 119 return nil, err 120 } 121 122 // only allow one connection so that we can detect any code paths that 123 // require more than one, which will deadlock if it's at the limit 124 dbConn.SetMaxOpenConns(1) 125 126 return dbConn, nil 127 } 128 129 func (runner *Runner) OpenDBAtVersion(version int) *sql.DB { 130 dbConn, err := runner.TryOpenDBAtVersion(version) 131 Expect(err).NotTo(HaveOccurred()) 132 return dbConn 133 } 134 135 func (runner *Runner) OpenDB() *sql.DB { 136 dbConn, err := migration.NewOpenHelper( 137 "postgres", 138 runner.DataSourceName(), 139 nil, 140 nil, 141 nil, 142 ).Open() 143 Expect(err).NotTo(HaveOccurred()) 144 145 // only allow one connection so that we can detect any code paths that 146 // require more than one, which will deadlock if it's at the limit 147 dbConn.SetMaxOpenConns(1) 148 149 return dbConn 150 } 151 152 func (runner *Runner) OpenConn() db.Conn { 153 dbConn, err := db.Open( 154 lagertest.NewTestLogger("postgres-runner"), 155 "postgres", 156 runner.DataSourceName(), 157 nil, 158 nil, 159 "postgresrunner", 160 nil, 161 ) 162 Expect(err).NotTo(HaveOccurred()) 163 164 // only allow one connection so that we can detect any code paths that 165 // require more than one, which will deadlock if it's at the limit 166 dbConn.SetMaxOpenConns(1) 167 168 return dbConn 169 } 170 171 func (runner *Runner) OpenSingleton() *sql.DB { 172 dbConn, err := sql.Open("postgres", runner.DataSourceName()) 173 Expect(err).NotTo(HaveOccurred()) 174 175 // only allow one connection, period. this matches production code use case, 176 // as this is used for advisory locks. 177 dbConn.SetMaxIdleConns(1) 178 dbConn.SetMaxOpenConns(1) 179 dbConn.SetConnMaxLifetime(0) 180 181 return dbConn 182 } 183 184 func (runner *Runner) DataSourceName() string { 185 return fmt.Sprintf("host=/tmp user=postgres dbname=testdb sslmode=disable port=%d", runner.Port) 186 } 187 188 func (runner *Runner) CreateTestDB() { 189 createdb := exec.Command("createdb", "-h", "/tmp", "-U", "postgres", "-p", strconv.Itoa(runner.Port), "testdb") 190 191 createS, err := gexec.Start(createdb, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter) 192 Expect(err).NotTo(HaveOccurred()) 193 194 <-createS.Exited 195 196 if createS.ExitCode() != 0 { 197 runner.DropTestDB() 198 199 createdb := exec.Command("createdb", "-h", "/tmp", "-U", "postgres", "-p", strconv.Itoa(runner.Port), "testdb") 200 createS, err = gexec.Start(createdb, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter) 201 Expect(err).NotTo(HaveOccurred()) 202 } 203 204 <-createS.Exited 205 206 Expect(createS).To(gexec.Exit(0)) 207 } 208 209 func (runner *Runner) DropTestDB() { 210 dropdb := exec.Command("dropdb", "-h", "/tmp", "-U", "postgres", "-p", strconv.Itoa(runner.Port), "testdb") 211 dropS, err := gexec.Start(dropdb, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter) 212 Expect(err).NotTo(HaveOccurred()) 213 214 <-dropS.Exited 215 216 Expect(dropS).To(gexec.Exit(0)) 217 } 218 219 func (runner *Runner) Truncate() { 220 truncate := exec.Command( 221 "psql", 222 "-h", "/tmp", 223 "-U", "postgres", 224 "-p", strconv.Itoa(runner.Port), 225 "testdb", 226 "-c", ` 227 SET client_min_messages TO WARNING; 228 229 CREATE OR REPLACE FUNCTION truncate_tables() RETURNS void AS $$ 230 DECLARE 231 statements CURSOR FOR 232 SELECT tablename FROM pg_tables 233 WHERE schemaname = 'public' AND tablename != 'migrations_history'; 234 BEGIN 235 FOR stmt IN statements LOOP 236 EXECUTE 'TRUNCATE TABLE ' || quote_ident(stmt.tablename) || ' RESTART IDENTITY CASCADE;'; 237 END LOOP; 238 END; 239 $$ LANGUAGE plpgsql; 240 241 CREATE OR REPLACE FUNCTION drop_ephemeral_sequences() RETURNS void AS $$ 242 DECLARE 243 statements CURSOR FOR 244 SELECT relname FROM pg_class 245 WHERE relname LIKE 'build_event_id_seq_%'; 246 BEGIN 247 FOR stmt IN statements LOOP 248 EXECUTE 'DROP SEQUENCE ' || quote_ident(stmt.relname) || ';'; 249 END LOOP; 250 END; 251 $$ LANGUAGE plpgsql; 252 253 CREATE OR REPLACE FUNCTION drop_ephemeral_tables() RETURNS void AS $$ 254 DECLARE 255 statements CURSOR FOR 256 SELECT relname FROM pg_class 257 WHERE relname LIKE 'pipeline_build_events_%' 258 AND relkind = 'r'; 259 team_statements CURSOR FOR 260 SELECT relname FROM pg_class 261 WHERE relname LIKE 'team_build_events_%' 262 AND relkind = 'r'; 263 BEGIN 264 FOR stmt IN statements LOOP 265 EXECUTE 'DROP TABLE ' || quote_ident(stmt.relname) || ';'; 266 END LOOP; 267 FOR stmt IN team_statements LOOP 268 EXECUTE 'DROP TABLE ' || quote_ident(stmt.relname) || ';'; 269 END LOOP; 270 END; 271 $$ LANGUAGE plpgsql; 272 273 CREATE OR REPLACE FUNCTION reset_global_sequences() RETURNS void AS $$ 274 DECLARE 275 statements CURSOR FOR 276 SELECT relname FROM pg_class 277 WHERE relname IN ('one_off_name', 'config_version_seq'); 278 BEGIN 279 FOR stmt IN statements LOOP 280 EXECUTE 'ALTER SEQUENCE ' || quote_ident(stmt.relname) || ' RESTART WITH 1;'; 281 END LOOP; 282 END; 283 $$ LANGUAGE plpgsql; 284 285 SELECT truncate_tables(); 286 SELECT drop_ephemeral_sequences(); 287 SELECT drop_ephemeral_tables(); 288 SELECT reset_global_sequences(); 289 `, 290 ) 291 292 truncateS, err := gexec.Start(truncate, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter) 293 Expect(err).NotTo(HaveOccurred()) 294 295 <-truncateS.Exited 296 297 Expect(truncateS).To(gexec.Exit(0)) 298 }