go.temporal.io/server@v1.23.0/common/persistence/tests/cassandra_test_util.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package tests 26 27 import ( 28 "fmt" 29 "os" 30 "path" 31 "path/filepath" 32 "sort" 33 "strings" 34 "testing" 35 "time" 36 37 "github.com/blang/semver/v4" 38 "github.com/gocql/gocql" 39 "go.uber.org/zap/zaptest" 40 41 "go.temporal.io/server/common/config" 42 "go.temporal.io/server/common/log" 43 "go.temporal.io/server/common/metrics" 44 p "go.temporal.io/server/common/persistence" 45 "go.temporal.io/server/common/persistence/cassandra" 46 commongocql "go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql" 47 "go.temporal.io/server/common/resolver" 48 "go.temporal.io/server/common/shuffle" 49 "go.temporal.io/server/environment" 50 ) 51 52 const ( 53 // TODO hard code this dir for now 54 // need to merge persistence test config / initialization in one place 55 testCassandraExecutionSchema = "../../../schema/cassandra/temporal/schema.cql" 56 testCassandraVisibilitySchema = "../../../schema/cassandra/visibility/schema.cql" 57 ) 58 59 // TODO merge the initialization with existing persistence setup 60 const ( 61 testCassandraClusterName = "temporal_cassandra_cluster" 62 63 testCassandraUser = "temporal" 64 testCassandraPassword = "temporal" 65 testCassandraDatabaseNamePrefix = "test_" 66 testCassandraDatabaseNameSuffix = "temporal_persistence" 67 ) 68 69 type ( 70 CassandraTestData struct { 71 Cfg *config.Cassandra 72 Factory *cassandra.Factory 73 Logger log.Logger 74 } 75 ) 76 77 func setUpCassandraTest(t *testing.T) (CassandraTestData, func()) { 78 var testData CassandraTestData 79 testData.Cfg = NewCassandraConfig() 80 testData.Logger = log.NewZapLogger(zaptest.NewLogger(t)) 81 SetUpCassandraDatabase(testData.Cfg, testData.Logger) 82 SetUpCassandraSchema(testData.Cfg, testData.Logger) 83 84 testData.Factory = cassandra.NewFactory( 85 *testData.Cfg, 86 resolver.NewNoopResolver(), 87 testCassandraClusterName, 88 testData.Logger, 89 metrics.NoopMetricsHandler, 90 ) 91 92 tearDown := func() { 93 testData.Factory.Close() 94 TearDownCassandraKeyspace(testData.Cfg) 95 } 96 97 return testData, tearDown 98 } 99 100 func SetUpCassandraDatabase(cfg *config.Cassandra, logger log.Logger) { 101 adminCfg := *cfg 102 // NOTE need to connect with empty name to create new database 103 adminCfg.Keyspace = "system" 104 105 session, err := commongocql.NewSession( 106 func() (*gocql.ClusterConfig, error) { 107 return commongocql.NewCassandraCluster(adminCfg, resolver.NewNoopResolver()) 108 }, 109 logger, 110 metrics.NoopMetricsHandler, 111 ) 112 if err != nil { 113 panic(fmt.Sprintf("unable to create Cassandra session: %v", err)) 114 } 115 defer session.Close() 116 117 if err := cassandra.CreateCassandraKeyspace( 118 session, 119 cfg.Keyspace, 120 1, 121 true, 122 log.NewNoopLogger(), 123 ); err != nil { 124 panic(fmt.Sprintf("unable to create Cassandra keyspace: %v", err)) 125 } 126 } 127 128 func SetUpCassandraSchema(cfg *config.Cassandra, logger log.Logger) { 129 ApplySchemaUpdate(cfg, testCassandraExecutionSchema, logger) 130 ApplySchemaUpdate(cfg, testCassandraVisibilitySchema, logger) 131 } 132 133 func ApplySchemaUpdate(cfg *config.Cassandra, schemaFile string, logger log.Logger) { 134 session, err := commongocql.NewSession( 135 func() (*gocql.ClusterConfig, error) { 136 return commongocql.NewCassandraCluster(*cfg, resolver.NewNoopResolver()) 137 }, 138 logger, 139 metrics.NoopMetricsHandler, 140 ) 141 if err != nil { 142 panic(err) 143 } 144 defer session.Close() 145 146 schemaPath, err := filepath.Abs(schemaFile) 147 if err != nil { 148 panic(err) 149 } 150 151 statements, err := p.LoadAndSplitQuery([]string{schemaPath}) 152 if err != nil { 153 panic(err) 154 } 155 156 for _, stmt := range statements { 157 if err = session.Query(stmt).Exec(); err != nil { 158 logger.Error(fmt.Sprintf("Unable to execute statement from file: %s\n %s", schemaFile, stmt)) 159 panic(err) 160 } 161 } 162 } 163 164 func TearDownCassandraKeyspace(cfg *config.Cassandra) { 165 adminCfg := *cfg 166 // NOTE need to connect with empty name to create new database 167 adminCfg.Keyspace = "system" 168 169 session, err := commongocql.NewSession( 170 func() (*gocql.ClusterConfig, error) { 171 return commongocql.NewCassandraCluster(adminCfg, resolver.NewNoopResolver()) 172 }, 173 log.NewNoopLogger(), 174 metrics.NoopMetricsHandler, 175 ) 176 if err != nil { 177 panic(fmt.Sprintf("unable to create Cassandra session: %v", err)) 178 } 179 defer session.Close() 180 181 if err := cassandra.DropCassandraKeyspace( 182 session, 183 cfg.Keyspace, 184 log.NewNoopLogger(), 185 ); err != nil { 186 panic(fmt.Sprintf("unable to drop Cassandra keyspace: %v", err)) 187 } 188 } 189 190 // GetSchemaFiles takes a root directory which contains subdirectories whose names are semantic versions and returns 191 // the .cql files within. E.g.: //schema/cassandra/temporal/versioned 192 // Subdirectories are ordered by semantic version, but files within the same subdirectory are in arbitrary order. 193 // All .cql files are returned regardless of whether they are named in manifest.json. 194 func GetSchemaFiles(schemaDir string, logger log.Logger) []string { 195 var retVal []string 196 197 versionDirPath := path.Join(schemaDir, "versioned") 198 subDirs, err := os.ReadDir(versionDirPath) 199 if err != nil { 200 panic(err) 201 } 202 203 versionDirNames := make([]string, 0, len(subDirs)) 204 for _, subDir := range subDirs { 205 if !subDir.IsDir() { 206 logger.Warn(fmt.Sprintf("Skipping non-directory file: '%s'", subDir.Name())) 207 continue 208 } 209 if _, ve := semver.ParseTolerant(subDir.Name()); ve != nil { 210 logger.Warn(fmt.Sprintf("Skipping directory which is not a valid semver: '%s'", subDir.Name())) 211 } 212 versionDirNames = append(versionDirNames, subDir.Name()) 213 } 214 215 sort.Slice(versionDirNames, func(i, j int) bool { 216 vLeft, err := semver.ParseTolerant(versionDirNames[i]) 217 if err != nil { 218 panic(err) // Logic error 219 } 220 vRight, err := semver.ParseTolerant(versionDirNames[j]) 221 if err != nil { 222 panic(err) // Logic error 223 } 224 return vLeft.Compare(vRight) < 0 225 }) 226 227 for _, dir := range versionDirNames { 228 vDirPath := path.Join(versionDirPath, dir) 229 files, err := os.ReadDir(vDirPath) 230 if err != nil { 231 panic(err) 232 } 233 for _, file := range files { 234 if file.IsDir() { 235 continue 236 } 237 if !strings.HasSuffix(file.Name(), ".cql") { 238 continue 239 } 240 retVal = append(retVal, path.Join(vDirPath, file.Name())) 241 } 242 } 243 244 return retVal 245 } 246 247 // NewCassandraConfig returns a new Cassandra config for test 248 func NewCassandraConfig() *config.Cassandra { 249 return &config.Cassandra{ 250 User: testCassandraUser, 251 Password: testCassandraPassword, 252 Hosts: environment.GetCassandraAddress(), 253 Port: environment.GetCassandraPort(), 254 Keyspace: testCassandraDatabaseNamePrefix + shuffle.String(testCassandraDatabaseNameSuffix), 255 ConnectTimeout: 30 * time.Second, 256 } 257 }