Files
tasks/tools/cassandra/cqlclient.go
Alex ShtinandGitHub 8e8f1b0f7c Remove license header from every file (#7689)
## What changed?
<!-- Describe what has changed in this PR -->
Remove license header from every file. Because it is really hard to
follow in this PR here is the summary:
1. License header is removed from all `.go` and `.proto` files
:fireworks::fireworks:🎆.
2. `LICENSE` file in the root directory has only Temporal and Uber
copyrights.
3. 5 other `LICENSE` files added to the packages which have copyrights
different from Temporal and Uber: Datadog, Xargin, "Mat Ryer, Tyler
Bunnell and contributors".
4. `license_file` flag is removed from all code generation tools.
5. `copyright_file` flag is removed from `go:generate mockgen`
directive.
6. All copyright related targets are removed from `Makefile`.
7. Updated Temporal copyright year to 2025 everywhere.

## Why?
<!-- Tell your future self why have you made these changes -->
I double checked with legal department that it is not needed to have
license header in every file. One file per repo is enough. I put all
copyrights to the root `LICENSE` file and removed header from all other
files. Also updated tools and `Makefile`.
2025-05-01 18:50:21 -07:00

306 lines
9.5 KiB
Go

package cassandra
import (
"context"
"fmt"
"time"
"github.com/gocql/gocql"
"go.temporal.io/server/common/auth"
"go.temporal.io/server/common/config"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/metrics"
commongocql "go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql"
"go.temporal.io/server/common/resolver"
"go.temporal.io/server/tools/common/schema"
)
type (
cqlClient struct {
nReplicas int
datacenter string
keyspace string
timeout time.Duration
session commongocql.Session
logger log.Logger
}
// CQLClientConfig contains the configuration for cql client
CQLClientConfig struct {
Hosts string
Port int
User string
Password string
AllowedAuthenticators []string
Keyspace string
Timeout int
numReplicas int
Datacenter string
Consistency string
TLS *auth.TLS
DisableInitialHostLookup bool
AddressTranslator *config.CassandraAddressTranslator
}
)
const (
defaultTimeout = 30 // Timeout in seconds
systemKeyspace = "system"
dbType = "cassandra"
)
const (
readSchemaVersionCQL = `SELECT curr_version from schema_version where keyspace_name=?`
listTablesCQL = `SELECT table_name from system_schema.tables where keyspace_name=?`
listTypesCQL = `SELECT type_name from system_schema.types where keyspace_name=?`
writeSchemaVersionCQL = `INSERT into schema_version(keyspace_name, creation_time, curr_version, min_compatible_version) VALUES (?,?,?,?)`
writeSchemaUpdateHistoryCQL = `INSERT into schema_update_history(year, month, update_time, old_version, new_version, manifest_md5, description) VALUES(?,?,?,?,?,?,?)`
createSchemaVersionTableCQL = `CREATE TABLE IF NOT EXISTS schema_version(keyspace_name text PRIMARY KEY, ` +
`creation_time timestamp, ` +
`curr_version text, ` +
`min_compatible_version text);`
createSchemaUpdateHistoryTableCQL = `CREATE TABLE IF NOT EXISTS schema_update_history(` +
`year int, ` +
`month int, ` +
`update_time timestamp, ` +
`description text, ` +
`manifest_md5 text, ` +
`new_version text, ` +
`old_version text, ` +
`PRIMARY KEY ((year, month), update_time));`
createKeyspaceCQL = `CREATE KEYSPACE IF NOT EXISTS %v ` +
`WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : %v};`
createKeyspaceNetworkTopologyCQL = `CREATE KEYSPACE IF NOT EXISTS %v ` +
`WITH replication = { 'class' : 'NetworkTopologyStrategy', '%v' : %v};`
)
var _ schema.DB = (*cqlClient)(nil)
// newCQLClient returns a new instance of CQLClient
func newCQLClient(cfg *CQLClientConfig, logger log.Logger) (*cqlClient, error) {
var err error
cassandraConfig := cfg.toCassandraConfig()
logger.Info("Validating connection to cassandra cluster.")
session, err := commongocql.NewSession(
func() (*gocql.ClusterConfig, error) {
return commongocql.NewCassandraCluster(*cassandraConfig, resolver.NewNoopResolver())
},
logger,
metrics.NoopMetricsHandler,
)
if err != nil {
logger.Error("Connection validation failed.", tag.Error(err))
return nil, err
}
logger.Info("Connection validation succeeded.")
return &cqlClient{
keyspace: cfg.Keyspace,
nReplicas: cfg.numReplicas,
datacenter: cfg.Datacenter,
timeout: time.Duration(cfg.Timeout) * time.Second,
session: session,
logger: logger,
}, nil
}
func (cfg *CQLClientConfig) toCassandraConfig() *config.Cassandra {
cassandraConfig := config.Cassandra{
Hosts: cfg.Hosts,
Port: cfg.Port,
User: cfg.User,
Password: cfg.Password,
AllowedAuthenticators: cfg.AllowedAuthenticators,
Keyspace: cfg.Keyspace,
TLS: cfg.TLS,
Datacenter: cfg.Datacenter,
DisableInitialHostLookup: cfg.DisableInitialHostLookup,
Consistency: &config.CassandraStoreConsistency{
Default: &config.CassandraConsistencySettings{
Consistency: cfg.Consistency,
},
},
AddressTranslator: cfg.AddressTranslator,
ConnectTimeout: time.Duration(cfg.Timeout) * time.Second,
}
return &cassandraConfig
}
func (client *cqlClient) CreateDatabase(name string) error {
return client.createKeyspace(name)
}
func (client *cqlClient) DropDatabase(name string) error {
return client.dropKeyspace(name)
}
// createKeyspace creates a cassandra Keyspace if it doesn't exist
func (client *cqlClient) createKeyspace(name string) error {
if client.datacenter != "" {
client.logger.Info(fmt.Sprintf("Creating Keyspace %v using NetworkTopologyStrategy in Datacenter %v with RF=%v.", name, client.datacenter, client.nReplicas))
return client.Exec(fmt.Sprintf(createKeyspaceNetworkTopologyCQL, name, client.datacenter, client.nReplicas))
}
client.logger.Info(fmt.Sprintf("Creating Keyspace %v using SimpleStrategy with RF=%v.", name, client.nReplicas))
return client.Exec(fmt.Sprintf(createKeyspaceCQL, name, client.nReplicas))
}
// dropKeyspace drops a Keyspace
func (client *cqlClient) dropKeyspace(name string) error {
return client.Exec(fmt.Sprintf("DROP KEYSPACE IF EXISTS %v", name))
}
func (client *cqlClient) DropAllTables() error {
return client.dropAllTablesTypes()
}
// CreateSchemaVersionTables sets up the schema version tables
func (client *cqlClient) CreateSchemaVersionTables() error {
if err := client.Exec(createSchemaVersionTableCQL); err != nil {
return err
}
return client.Exec(createSchemaUpdateHistoryTableCQL)
}
// ReadSchemaVersion returns the current schema version for the Keyspace
func (client *cqlClient) ReadSchemaVersion() (string, error) {
query := client.session.Query(readSchemaVersionCQL, client.keyspace)
iter := query.Iter()
var version string
success := iter.Scan(&version)
err := iter.Close()
if err == nil && !success {
err = fmt.Errorf("no schema version found for keyspace %q", client.keyspace)
}
if err != nil {
return "", fmt.Errorf("unable to get current schema version from Cassandra: %w", err)
}
return version, nil
}
// UpdateShemaVersion updates the schema version for the Keyspace
func (client *cqlClient) UpdateSchemaVersion(newVersion string, minCompatibleVersion string) error {
query := client.session.Query(writeSchemaVersionCQL, client.keyspace, time.Now().UTC(), newVersion, minCompatibleVersion)
return query.Exec()
}
// WriteSchemaUpdateLog adds an entry to the schema update history table
func (client *cqlClient) WriteSchemaUpdateLog(oldVersion string, newVersion string, manifestMD5 string, desc string) error {
now := time.Now().UTC()
query := client.session.Query(writeSchemaUpdateHistoryCQL)
query.Bind(now.Year(), int(now.Month()), now, oldVersion, newVersion, manifestMD5, desc)
return query.Exec()
}
// Exec executes a cql statement
func (client *cqlClient) Exec(stmt string, args ...interface{}) error {
if err := client.session.Query(stmt, args...).Exec(); err != nil {
return err
}
return client.waitSchemaAgreement()
}
// Close closes the cql client
func (client *cqlClient) Close() {
if client.session != nil {
client.session.Close()
}
}
// ListTables lists the table names in a Keyspace
func (client *cqlClient) ListTables() ([]string, error) {
query := client.session.Query(listTablesCQL, client.keyspace)
iter := query.Iter()
var names []string
var name string
for iter.Scan(&name) {
names = append(names, name)
}
if err := iter.Close(); err != nil {
return nil, err
}
return names, nil
}
// listTypes lists the User defined types in a Keyspace
func (client *cqlClient) listTypes() ([]string, error) {
qry := client.session.Query(listTypesCQL, client.keyspace)
iter := qry.Iter()
var names []string
var name string
for iter.Scan(&name) {
names = append(names, name)
}
if err := iter.Close(); err != nil {
return nil, err
}
return names, nil
}
// dropTable drops a given table from the Keyspace
func (client *cqlClient) dropTable(name string) error {
return client.Exec(fmt.Sprintf("DROP TABLE %v", name))
}
// dropType drops a given type from the Keyspace
func (client *cqlClient) dropType(name string) error {
return client.Exec(fmt.Sprintf("DROP TYPE %v", name))
}
// dropAllTablesTypes deletes all tables/types in the
// Keyspace without deleting the Keyspace
func (client *cqlClient) dropAllTablesTypes() error {
tables, err := client.ListTables()
if err != nil {
return err
}
client.logger.Info(fmt.Sprintf("Dropping following tables: %v.", tables))
for _, table := range tables {
err1 := client.dropTable(table)
if err1 != nil {
client.logger.Error(fmt.Sprintf("Error dropping table %v.", table), tag.Error(err1))
}
}
types, err := client.listTypes()
if err != nil {
return err
}
client.logger.Info(fmt.Sprintf("Dropping following types: %v.", types))
numOfTypes := len(types)
for i := 0; i < numOfTypes && len(types) > 0; i++ {
var erroredTypes []string
for _, t := range types {
err = client.dropType(t)
if err != nil {
client.logger.Error(fmt.Sprintf("Error dropping type %v.", t), tag.Error(err))
erroredTypes = append(erroredTypes, t)
}
}
types = erroredTypes
}
if len(types) > 0 {
return err
}
return nil
}
// waitSchemaAgreement wait for schema change agreements
func (client *cqlClient) waitSchemaAgreement() error {
ctx, cancel := context.WithTimeout(context.Background(), client.timeout)
defer cancel()
return client.session.AwaitSchemaAgreement(ctx)
}
// Type gives the type of db
func (client *cqlClient) Type() string {
return dbType
}