From 2ace558a8e00665b16b4e698f830012da6d4a64c Mon Sep 17 00:00:00 2001 From: Marco Cadetg Date: Fri, 12 Apr 2024 14:39:32 +0200 Subject: [PATCH 1/3] use mysql connection pool --- cmd/main.go | 9 ++++- .../crd.lagoon.sh_databasemysqlproviders.yaml | 2 + .../bases/crd.lagoon.sh_databaserequests.yaml | 2 +- internal/database/mysql/mysql.go | 40 ++++++++++++------- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index b22f03d..a3a72aa 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,6 +18,7 @@ package main import ( "crypto/tls" + "database/sql" "flag" "os" @@ -126,10 +127,14 @@ func main() { os.Exit(1) } + mysqlClient := &mysql.MySQLImpl{ + ConnectionCache: make(map[string]*sql.DB), + } + if err = (&controller.DatabaseRequestReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - MySQLClient: &mysql.MySQLImpl{}, + MySQLClient: mysqlClient, }).SetupWithManager(mgr, maxConcurrentReconciles); err != nil { setupLog.Error(err, "unable to create controller", "controller", "DatabaseRequest") os.Exit(1) @@ -137,7 +142,7 @@ func main() { if err = (&controller.DatabaseMySQLProviderReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - MySQLClient: &mysql.MySQLImpl{}, + MySQLClient: mysqlClient, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "DatabaseMySQLProvider") os.Exit(1) diff --git a/config/crd/bases/crd.lagoon.sh_databasemysqlproviders.yaml b/config/crd/bases/crd.lagoon.sh_databasemysqlproviders.yaml index 80cb76c..230de27 100644 --- a/config/crd/bases/crd.lagoon.sh_databasemysqlproviders.yaml +++ b/config/crd/bases/crd.lagoon.sh_databasemysqlproviders.yaml @@ -191,6 +191,8 @@ spec: description: MySQLConnectionStatus provides the status of the MySQL database items: + description: MySQLConnectionStatus defines the status of a MySQL + database connection properties: enabled: description: Enabled is a flag to indicate whether a MySQL database diff --git a/config/crd/bases/crd.lagoon.sh_databaserequests.yaml b/config/crd/bases/crd.lagoon.sh_databaserequests.yaml index ff0e090..de5711e 100644 --- a/config/crd/bases/crd.lagoon.sh_databaserequests.yaml +++ b/config/crd/bases/crd.lagoon.sh_databaserequests.yaml @@ -42,7 +42,7 @@ spec: additionalUsers: description: AdditionalUsers defines the additional users to be created items: - description: AdditionalUser defines the additional users to be created + description: AdditionalUser defines the additional user to be created properties: name: description: |- diff --git a/internal/database/mysql/mysql.go b/internal/database/mysql/mysql.go index 57e0c46..b2f8036 100644 --- a/internal/database/mysql/mysql.go +++ b/internal/database/mysql/mysql.go @@ -53,19 +53,35 @@ type MySQLInterface interface { } // MySQLImpl is the implementation of the MySQL database -type MySQLImpl struct{} +// Note that we maintain a connection cache to avoid opening a new connection for each operation. +type MySQLImpl struct { + ConnectionCache map[string]*sql.DB +} // Make sure MySQLImpl implements MySQLInterface var _ MySQLInterface = (*MySQLImpl)(nil) +// getConnection returns a connection to the MySQL database +func (mi *MySQLImpl) getConnection(ctx context.Context, dsn string) (*sql.DB, error) { + if db, ok := mi.ConnectionCache[dsn]; ok { + return db, nil + } + db, err := sql.Open("mysql", dsn) + if err != nil { + return nil, fmt.Errorf("failed to open MySQL database: %w", err) + } + log.FromContext(ctx).Info("Opening MySQL database connection") + mi.ConnectionCache[dsn] = db + return db, nil +} + // Ping pings the MySQL database func (mi *MySQLImpl) Ping(ctx context.Context, dsn string) error { log.FromContext(ctx).Info("Pinging MySQL database") - db, err := sql.Open("mysql", dsn) + db, err := mi.getConnection(ctx, dsn) if err != nil { return fmt.Errorf("ping failed to open MySQL database: %w", err) } - defer db.Close() if err := db.PingContext(ctx); err != nil { return fmt.Errorf("failed to ping MySQL database: %w", err) @@ -77,11 +93,10 @@ func (mi *MySQLImpl) Ping(ctx context.Context, dsn string) error { // Version returns the version of the MySQL database func (mi *MySQLImpl) Version(ctx context.Context, dsn string) (string, error) { log.FromContext(ctx).Info("Getting MySQL database version") - db, err := sql.Open("mysql", dsn) + db, err := mi.getConnection(ctx, dsn) if err != nil { return "", fmt.Errorf("version failed to open MySQL database: %w", err) } - defer db.Close() var version string err = db.QueryRowContext(ctx, "SELECT VERSION()").Scan(&version) @@ -96,11 +111,10 @@ func (mi *MySQLImpl) Version(ctx context.Context, dsn string) (string, error) { // Note it doesn't include CPU or memory usage which could be obtained from other sources. func (mi *MySQLImpl) Load(ctx context.Context, dsn string) (int, error) { log.FromContext(ctx).Info("Getting MySQL database load") - db, err := sql.Open("mysql", dsn) + db, err := mi.getConnection(ctx, dsn) if err != nil { return 0, fmt.Errorf("load failed to open MySQL database: %w", err) } - defer db.Close() query := ` SELECT SUM(data_length + index_length) AS total_size @@ -121,11 +135,10 @@ func (mi *MySQLImpl) Initialize(ctx context.Context, dsn string) error { log.FromContext(ctx).Info("Initializing MySQL database") // Connect to MySQL server without specifying a database - db, err := sql.Open("mysql", dsn) + db, err := mi.getConnection(ctx, dsn) if err != nil { return err } - defer db.Close() // Create the database if it doesn't exist _, err = db.ExecContext(ctx, "CREATE DATABASE IF NOT EXISTS dbaas_controller") @@ -182,11 +195,10 @@ func (mi *MySQLImpl) databaseInfo(ctx context.Context, dsn, namespace, name stri info := DatabaseInfo{} // Connect to MySQL server and select the dbaas_controller database - db, err := sql.Open("mysql", dsn) + db, err := mi.getConnection(ctx, dsn) if err != nil { return info, fmt.Errorf("failed to connect to MySQL server: %w", err) } - defer db.Close() _, err = db.ExecContext(ctx, "USE dbaas_controller") if err != nil { @@ -240,11 +252,10 @@ func (mi *MySQLImpl) CreateDatabase(ctx context.Context, dsn, name, namespace st return info, fmt.Errorf("failed to get database info: %w", err) } // Connect to the database server - db, err := sql.Open("mysql", dsn) + db, err := mi.getConnection(ctx, dsn) if err != nil { return info, fmt.Errorf("create database error connecting to the database server: %w", err) } - defer db.Close() // Ping the database to verify connection establishment. if err := db.PingContext(ctx); err != nil { @@ -294,11 +305,10 @@ func (mi *MySQLImpl) DropDatabase(ctx context.Context, dsn, name, namespace stri } // Connect to the database server - db, err := sql.Open("mysql", dsn) + db, err := mi.getConnection(ctx, dsn) if err != nil { return fmt.Errorf("drop database error connecting to the database server: %w", err) } - defer db.Close() // Ping the database to verify connection establishment. if err := db.PingContext(ctx); err != nil { From 0af7588a9ea61499341d776e75d0e7645deb8cb0 Mon Sep 17 00:00:00 2001 From: shreddedbacon Date: Tue, 30 Apr 2024 09:21:28 +1000 Subject: [PATCH 2/3] test: add gh action --- .github/workflows/test-suite.yaml | 31 +++++++++++++++++++++++++++++++ Makefile | 25 +++++++++++++++++++++---- kind-config.yaml | 2 -- 3 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/test-suite.yaml diff --git a/.github/workflows/test-suite.yaml b/.github/workflows/test-suite.yaml new file mode 100644 index 0000000..cdc8eb9 --- /dev/null +++ b/.github/workflows/test-suite.yaml @@ -0,0 +1,31 @@ +name: DBaaS controller tests + +on: pull_request + +jobs: + test-suite: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: "0" + + - name: Configure kind network + run: | + docker network create kind + + - name: Create kind cluster + uses: helm/kind-action@v1.9.0 + with: + version: v0.22.0 + node_image: kindest/node:v1.28.7@sha256:9bc6c451a289cf96ad0bbaf33d416901de6fd632415b076ab05f5fa7e4f65c58 + kubectl_version: v1.28.7 + cluster_name: kind + config: kind-config.yaml + + - name: Run github/test-e2e + run: make github/test-e2e diff --git a/Makefile b/Makefile index 79934b0..be90b53 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ else GOBIN=$(shell go env GOBIN) endif +KIND_CLUSTER = kind + # CONTAINER_TOOL defines the container tool to be used for building images. # Be aware that the target commands are only tested with Docker which is # scaffolded by default. However, you might want to replace it to use other @@ -64,10 +66,25 @@ vet: ## Run go vet against code. test: manifests generate fmt vet envtest ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out +.PHONY: create-kind-cluster +create-kind-cluster: + docker network inspect $(KIND_CLUSTER) >/dev/null || docker network create $(KIND_CLUSTER) + kind create cluster --wait=60s --name=$(KIND_CLUSTER) --config=kind-config.yaml + +.PHONY: delete-kind-cluster +delete-kind-cluster: + kind delete cluster --name=$(KIND_CLUSTER) && docker network rm $(KIND_CLUSTER) + # Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors. -.PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up. -test-e2e: - kind export kubeconfig --name=kind +.PHONY: github/test-e2e # Run the e2e tests against a Kind k8s instance that is spun up inside github action. +github/test-e2e: + go test ./test/e2e/ -v -ginkgo.v + +# Create a kind cluster locally and run the test e2e test suite against it +.PHONY: local-kind/test-e2e # Run the e2e tests against a Kind k8s instance that is spun up locally +local-kind/test-e2e: create-kind-cluster + export KIND_CLUSTER=$(KIND_CLUSTER) && \ + kind export kubeconfig --name=$(KIND_CLUSTER) && \ go test ./test/e2e/ -v -ginkgo.v .PHONY: lint @@ -201,4 +218,4 @@ echo "Downloading $${package}" ;\ GOBIN=$(LOCALBIN) go install $${package} ;\ mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\ } -endef +endef \ No newline at end of file diff --git a/kind-config.yaml b/kind-config.yaml index 45dc76c..752e993 100644 --- a/kind-config.yaml +++ b/kind-config.yaml @@ -1,8 +1,6 @@ kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 -name: dbaas-controller-cluster nodes: - role: control-plane - role: worker - role: worker -image: kindest/node:v1.29.2 \ No newline at end of file From cb1e3ef4422d9c341a440db8c20fed630b19fcda Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 3 May 2024 08:07:58 +1000 Subject: [PATCH 3/3] chore: update Makefile Co-authored-by: Marco Cadetg --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index be90b53..59a0e58 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ else GOBIN=$(shell go env GOBIN) endif -KIND_CLUSTER = kind +KIND_CLUSTER ?= kind # CONTAINER_TOOL defines the container tool to be used for building images. # Be aware that the target commands are only tested with Docker which is