go.temporal.io/server@v1.23.0/common/persistence/cassandra/test.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 cassandra
    26  
    27  import (
    28  	"path"
    29  	"strings"
    30  	"time"
    31  
    32  	"github.com/gocql/gocql"
    33  	"go.temporal.io/server/common"
    34  	"go.temporal.io/server/common/backoff"
    35  	"go.temporal.io/server/common/config"
    36  	"go.temporal.io/server/common/debug"
    37  	"go.temporal.io/server/common/dynamicconfig"
    38  	"go.temporal.io/server/common/log"
    39  	"go.temporal.io/server/common/log/tag"
    40  	"go.temporal.io/server/common/metrics"
    41  	p "go.temporal.io/server/common/persistence"
    42  	commongocql "go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql"
    43  	"go.temporal.io/server/common/resolver"
    44  	"go.temporal.io/server/environment"
    45  	"go.temporal.io/server/tests/testutils"
    46  )
    47  
    48  const (
    49  	testSchemaDir = "schema/cassandra/"
    50  )
    51  
    52  // TestCluster allows executing cassandra operations in testing.
    53  type TestCluster struct {
    54  	keyspace       string
    55  	schemaDir      string
    56  	session        commongocql.Session
    57  	cfg            config.Cassandra
    58  	faultInjection *config.FaultInjection
    59  	logger         log.Logger
    60  }
    61  
    62  // NewTestCluster returns a new cassandra test cluster
    63  func NewTestCluster(keyspace, username, password, host string, port int, schemaDir string, faultInjection *config.FaultInjection, logger log.Logger) *TestCluster {
    64  	var result TestCluster
    65  	result.logger = logger
    66  	result.keyspace = keyspace
    67  	if port == 0 {
    68  		port = environment.GetCassandraPort()
    69  	}
    70  	if schemaDir == "" {
    71  		schemaDir = testSchemaDir
    72  	}
    73  	if host == "" {
    74  		host = environment.GetCassandraAddress()
    75  	}
    76  	result.schemaDir = schemaDir
    77  	result.cfg = config.Cassandra{
    78  		User:           username,
    79  		Password:       password,
    80  		Hosts:          host,
    81  		Port:           port,
    82  		MaxConns:       2,
    83  		ConnectTimeout: 30 * time.Second * debug.TimeoutMultiplier,
    84  		Keyspace:       keyspace,
    85  	}
    86  	result.faultInjection = faultInjection
    87  	return &result
    88  }
    89  
    90  // Config returns the persistence config for connecting to this test cluster
    91  func (s *TestCluster) Config() config.Persistence {
    92  	cfg := s.cfg
    93  	return config.Persistence{
    94  		DefaultStore:    "test",
    95  		VisibilityStore: "test",
    96  		DataStores: map[string]config.DataStore{
    97  			"test": {Cassandra: &cfg, FaultInjection: s.faultInjection},
    98  		},
    99  		TransactionSizeLimit: dynamicconfig.GetIntPropertyFn(common.DefaultTransactionSizeLimit),
   100  	}
   101  }
   102  
   103  // DatabaseName from PersistenceTestCluster interface
   104  func (s *TestCluster) DatabaseName() string {
   105  	return s.keyspace
   106  }
   107  
   108  // SetupTestDatabase from PersistenceTestCluster interface
   109  func (s *TestCluster) SetupTestDatabase() {
   110  	s.CreateSession("system")
   111  	s.CreateDatabase()
   112  	s.CreateSession(s.DatabaseName())
   113  	schemaDir := s.schemaDir + "/"
   114  
   115  	if !strings.HasPrefix(schemaDir, "/") && !strings.HasPrefix(schemaDir, "../") {
   116  		temporalPackageDir := testutils.GetRepoRootDirectory()
   117  		schemaDir = path.Join(temporalPackageDir, schemaDir)
   118  	}
   119  
   120  	s.LoadSchema(path.Join(schemaDir, "temporal", "schema.cql"))
   121  	s.LoadSchema(path.Join(schemaDir, "visibility", "schema.cql"))
   122  }
   123  
   124  // TearDownTestDatabase from PersistenceTestCluster interface
   125  func (s *TestCluster) TearDownTestDatabase() {
   126  	s.DropDatabase()
   127  	s.session.Close()
   128  }
   129  
   130  // CreateSession from PersistenceTestCluster interface
   131  func (s *TestCluster) CreateSession(
   132  	keyspace string,
   133  ) {
   134  	if s.session != nil {
   135  		s.session.Close()
   136  	}
   137  
   138  	var err error
   139  	op := func() error {
   140  		session, err := commongocql.NewSession(
   141  			func() (*gocql.ClusterConfig, error) {
   142  				return commongocql.NewCassandraCluster(
   143  					config.Cassandra{
   144  						Hosts:    s.cfg.Hosts,
   145  						Port:     s.cfg.Port,
   146  						User:     s.cfg.User,
   147  						Password: s.cfg.Password,
   148  						Keyspace: keyspace,
   149  						Consistency: &config.CassandraStoreConsistency{
   150  							Default: &config.CassandraConsistencySettings{
   151  								Consistency: "ONE",
   152  							},
   153  						},
   154  						ConnectTimeout: s.cfg.ConnectTimeout,
   155  					},
   156  					resolver.NewNoopResolver(),
   157  				)
   158  			},
   159  			log.NewNoopLogger(),
   160  			metrics.NoopMetricsHandler,
   161  		)
   162  		if err == nil {
   163  			s.session = session
   164  		}
   165  		return err
   166  	}
   167  	err = backoff.ThrottleRetry(
   168  		op,
   169  		backoff.NewExponentialRetryPolicy(time.Second).WithExpirationInterval(time.Minute),
   170  		nil,
   171  	)
   172  	if err != nil {
   173  		s.logger.Fatal("CreateSession", tag.Error(err))
   174  	}
   175  	s.logger.Debug("created session", tag.NewStringTag("keyspace", keyspace))
   176  }
   177  
   178  // CreateDatabase from PersistenceTestCluster interface
   179  func (s *TestCluster) CreateDatabase() {
   180  	err := CreateCassandraKeyspace(s.session, s.DatabaseName(), 1, true, s.logger)
   181  	if err != nil {
   182  		s.logger.Fatal("CreateCassandraKeyspace", tag.Error(err))
   183  	}
   184  	s.logger.Info("created database", tag.NewStringTag("database", s.DatabaseName()))
   185  }
   186  
   187  // DropDatabase from PersistenceTestCluster interface
   188  func (s *TestCluster) DropDatabase() {
   189  	err := DropCassandraKeyspace(s.session, s.DatabaseName(), s.logger)
   190  	if err != nil && !strings.Contains(err.Error(), "AlreadyExists") {
   191  		s.logger.Fatal("DropCassandraKeyspace", tag.Error(err))
   192  	}
   193  	s.logger.Info("dropped database", tag.NewStringTag("database", s.DatabaseName()))
   194  }
   195  
   196  // LoadSchema from PersistenceTestCluster interface
   197  func (s *TestCluster) LoadSchema(schemaFile string) {
   198  	statements, err := p.LoadAndSplitQuery([]string{schemaFile})
   199  	if err != nil {
   200  		s.logger.Fatal("LoadSchema", tag.Error(err))
   201  	}
   202  	for _, stmt := range statements {
   203  		if err = s.session.Query(stmt).Exec(); err != nil {
   204  			s.logger.Fatal("LoadSchema", tag.Error(err))
   205  		}
   206  	}
   207  	s.logger.Info("loaded schema")
   208  }
   209  
   210  func (s *TestCluster) GetSession() commongocql.Session {
   211  	return s.session
   212  }