-
Notifications
You must be signed in to change notification settings - Fork 306
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: scylladb database provider implementation (#320)
- Loading branch information
1 parent
fca0244
commit ff91dce
Showing
16 changed files
with
620 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,6 +104,8 @@ var ( | |
sqliteDriver = []string{"github.com/mattn/go-sqlite3"} | ||
redisDriver = []string{"github.com/redis/go-redis/v9"} | ||
mongoDriver = []string{"go.mongodb.org/mongo-driver"} | ||
gocqlDriver = []string{"github.com/gocql/gocql"} | ||
scyllaDriver = "github.com/scylladb/[email protected]" // Replacement for GoCQL | ||
|
||
godotenvPackage = []string{"github.com/joho/godotenv"} | ||
templPackage = []string{"github.com/a-h/templ"} | ||
|
@@ -206,6 +208,11 @@ func (p *Project) createDBDriverMap() { | |
packageName: redisDriver, | ||
templater: dbdriver.RedisTemplate{}, | ||
} | ||
|
||
p.DBDriverMap[flags.Scylla] = Driver{ | ||
packageName: gocqlDriver, | ||
templater: dbdriver.ScyllaTemplate{}, | ||
} | ||
} | ||
|
||
func (p *Project) createDockerMap() { | ||
|
@@ -227,6 +234,10 @@ func (p *Project) createDockerMap() { | |
packageName: []string{}, | ||
templater: docker.RedisDockerTemplate{}, | ||
} | ||
p.DockerMap[flags.Scylla] = Docker{ | ||
packageName: []string{}, | ||
templater: docker.ScyllaDockerTemplate{}, | ||
} | ||
} | ||
|
||
// CreateMainFile creates the project folders and files, | ||
|
@@ -335,12 +346,22 @@ func (p *Project) CreateMainFile() error { | |
|
||
// Install the godotenv package | ||
err = utils.GoGetPackage(projectPath, godotenvPackage) | ||
|
||
if err != nil { | ||
log.Printf("Could not install go dependency %v\n", err) | ||
|
||
return err | ||
} | ||
|
||
if p.DBDriver == flags.Scylla { | ||
replace := fmt.Sprintf("%s=%s", gocqlDriver[0], scyllaDriver) | ||
err = utils.GoModReplace(projectPath, replace) | ||
if err != nil { | ||
log.Printf("Could not replace go dependency %v\n", err) | ||
return err | ||
} | ||
} | ||
|
||
err = p.CreatePath(cmdApiPath, projectPath) | ||
if err != nil { | ||
log.Printf("Error creating path: %s", projectPath) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{{- if .AdvancedOptions.docker }} | ||
# BLUEPRINT_DB_HOSTS=scylla_bp:9042 # ScyllaDB default port | ||
BLUEPRINT_DB_HOSTS=scylla_bp:19042 # ScyllaDB Shard-Aware port | ||
{{- else }} | ||
# BLUEPRINT_DB_HOSTS=localhost:9042 # ScyllaDB default port | ||
BLUEPRINT_DB_HOSTS=localhost:19042 # ScyllaDB Shard-Aware port | ||
{{- end }} | ||
BLUEPRINT_DB_CONSISTENCY="LOCAL_QUORUM" | ||
# BLUEPRINT_DB_USERNAME= | ||
# BLUEPRINT_DB_PASSWORD= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
package database | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"os" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/gocql/gocql" | ||
_ "github.com/joho/godotenv/autoload" | ||
) | ||
|
||
// Service defines the interface for health checks. | ||
type Service interface { | ||
Health() map[string]string | ||
Close() error | ||
} | ||
|
||
// service implements the Service interface. | ||
type service struct { | ||
Session *gocql.Session | ||
} | ||
|
||
// Environment variables for ScyllaDB connection. | ||
var ( | ||
hosts = os.Getenv("BLUEPRINT_DB_HOSTS") // Comma-separated list of hosts:port | ||
username = os.Getenv("BLUEPRINT_DB_USERNAME") // Username for authentication | ||
password = os.Getenv("BLUEPRINT_DB_PASSWORD") // Password for authentication | ||
consistencyLevel = os.Getenv("BLUEPRINT_DB_CONSISTENCY") // Consistency level | ||
) | ||
|
||
// New initializes a new Service with a ScyllaDB Session. | ||
func New() Service { | ||
cluster := gocql.NewCluster(strings.Split(hosts, ",")...) | ||
cluster.PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(gocql.RoundRobinHostPolicy()) | ||
|
||
// Set authentication if provided | ||
if username != "" && password != "" { | ||
cluster.Authenticator = gocql.PasswordAuthenticator{ | ||
Username: username, | ||
Password: password, | ||
} | ||
} | ||
|
||
// Set consistency level if provided | ||
if consistencyLevel != "" { | ||
if cl, err := parseConsistency(consistencyLevel); err == nil { | ||
cluster.Consistency = cl | ||
} else { | ||
log.Printf("Invalid SCYLLA_DB_CONSISTENCY '%s', using default: %v", consistencyLevel, err) | ||
} | ||
} | ||
|
||
// Create Session | ||
session, err := cluster.CreateSession() | ||
if err != nil { | ||
log.Fatalf("Failed to connect to ScyllaDB cluster: %v", err) | ||
} | ||
|
||
s := &service{Session: session} | ||
return s | ||
} | ||
|
||
// parseConsistency converts a string to a gocql.Consistency value. | ||
func parseConsistency(cons string) (gocql.Consistency, error) { | ||
consistencyMap := map[string]gocql.Consistency{ | ||
"ANY": gocql.Any, | ||
"ONE": gocql.One, | ||
"TWO": gocql.Two, | ||
"THREE": gocql.Three, | ||
"QUORUM": gocql.Quorum, | ||
"ALL": gocql.All, | ||
"LOCAL_ONE": gocql.LocalOne, | ||
"LOCAL_QUORUM": gocql.LocalQuorum, | ||
"EACH_QUORUM": gocql.EachQuorum, | ||
} | ||
|
||
if consistency, ok := consistencyMap[strings.ToUpper(cons)]; ok { | ||
return consistency, nil | ||
} | ||
return gocql.LocalQuorum, fmt.Errorf("unknown consistency level: %s", cons) | ||
} | ||
|
||
// Health returns the health status and statistics of the ScyllaDB cluster. | ||
func (s *service) Health() map[string]string { | ||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||
defer cancel() | ||
|
||
stats := make(map[string]string) | ||
|
||
// Check ScyllaDB health and populate the stats map | ||
startedAt := time.Now() | ||
|
||
// Execute a simple query to check connectivity | ||
query := "SELECT now() FROM system.local" | ||
iter := s.Session.Query(query).WithContext(ctx).Iter() | ||
var currentTime time.Time | ||
if !iter.Scan(¤tTime) { | ||
if err := iter.Close(); err != nil { | ||
stats["status"] = "down" | ||
stats["message"] = fmt.Sprintf("Failed to execute query: %v", err) | ||
return stats | ||
} | ||
} | ||
if err := iter.Close(); err != nil { | ||
stats["status"] = "down" | ||
stats["message"] = fmt.Sprintf("Error during query execution: %v", err) | ||
return stats | ||
} | ||
|
||
// ScyllaDB is up | ||
stats["status"] = "up" | ||
stats["message"] = "It's healthy" | ||
stats["scylla_current_time"] = currentTime.String() | ||
|
||
// Retrieve cluster information | ||
// Get keyspace information | ||
getKeyspacesQuery := "SELECT keyspace_name FROM system_schema.keyspaces" | ||
keyspacesIterator := s.Session.Query(getKeyspacesQuery).Iter() | ||
|
||
stats["scylla_keyspaces"] = strconv.Itoa(keyspacesIterator.NumRows()) | ||
if err := keyspacesIterator.Close(); err != nil { | ||
log.Fatalf("Failed to close keyspaces iterator: %v", err) | ||
} | ||
|
||
// Get cluster information | ||
var currentDatacenter string | ||
var currentHostStatus bool | ||
|
||
var clusterNodesUp uint | ||
var clusterNodesDown uint | ||
var clusterSize uint | ||
|
||
clusterNodesIterator := s.Session.Query("SELECT dc, up FROM system.cluster_status").Iter() | ||
for clusterNodesIterator.Scan(¤tDatacenter, ¤tHostStatus) { | ||
clusterSize++ | ||
if currentHostStatus { | ||
clusterNodesUp++ | ||
} else { | ||
clusterNodesDown++ | ||
} | ||
} | ||
|
||
if err := clusterNodesIterator.Close(); err != nil { | ||
log.Fatalf("Failed to close cluster nodes iterator: %v", err) | ||
} | ||
|
||
stats["scylla_cluster_size"] = strconv.Itoa(int(clusterSize)) | ||
stats["scylla_cluster_nodes_up"] = strconv.Itoa(int(clusterNodesUp)) | ||
stats["scylla_cluster_nodes_down"] = strconv.Itoa(int(clusterNodesDown)) | ||
stats["scylla_current_datacenter"] = currentDatacenter | ||
|
||
// Calculate the time taken to perform the health check | ||
stats["scylla_health_check_duration"] = time.Since(startedAt).String() | ||
return stats | ||
} | ||
|
||
// Close gracefully closes the ScyllaDB Session. | ||
func (s *service) Close() error { | ||
s.Session.Close() | ||
return nil | ||
} |
Oops, something went wrong.
ff91dce
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can I get the whole code process