From eae146c7a6e0d4b41155308939bce68ac93a9900 Mon Sep 17 00:00:00 2001 From: bjeevan-ib Date: Tue, 10 Oct 2023 13:23:54 -0700 Subject: [PATCH 1/2] support system function --- Makefile | 3 +- controllers/databaseclaim_controller.go | 10 +++++++ helm/db-controller/values.yaml | 7 +++++ pkg/dbclient/client.go | 38 +++++++++++++++++++++++++ pkg/dbclient/client_test.go | 32 +++++++++++++++++++++ pkg/dbclient/interface.go | 1 + 6 files changed, 89 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 23f4c65f..e3e99853 100644 --- a/Makefile +++ b/Makefile @@ -354,8 +354,7 @@ deploy: .id docker-push build-chart --create-namespace \ -f helm/db-controller/minikube.yaml \ --set dbController.class=`cat .id` \ - --set image.tag="${TAG}" \ - --set db.identifier.prefix=`cat .id` ${HELM_SETFLAGS} + --set image.tag="${TAG}" ${HELM_SETFLAGS} undeploy: .id helm delete --namespace `cat .id` `cat .id`-db-ctrl diff --git a/controllers/databaseclaim_controller.go b/controllers/databaseclaim_controller.go index ae32d55f..4f9d0098 100644 --- a/controllers/databaseclaim_controller.go +++ b/controllers/databaseclaim_controller.go @@ -448,6 +448,8 @@ func (r *DatabaseClaimReconciler) updateStatus(ctx context.Context, dbClaim *per if err != nil { return r.manageError(ctx, dbClaim, err) } + dbClaim.Status.ActiveDB = *dbClaim.Status.NewDB.DeepCopy() + dbClaim.Status.NewDB = persistancev1.Status{ConnectionInfo: &persistancev1.DatabaseClaimConnectionInfo{}} } return r.reconcileMigrateToNewDB(ctx, dbClaim) @@ -540,6 +542,10 @@ func (r *DatabaseClaimReconciler) reconcileUseExistingDB(ctx context.Context, db return err } } + err = dbClient.ManageSystemFunctions(dbName, r.getSystemFunctions()) + if err != nil { + return err + } return nil } @@ -1072,6 +1078,10 @@ func (r *DatabaseClaimReconciler) getDbSubnetGroupNameRef() string { return r.Config.GetString("dbSubnetGroupNameRef") } +func (r *DatabaseClaimReconciler) getSystemFunctions() map[string]string { + return r.Config.GetStringMapString("system-functions") +} + func (r *DatabaseClaimReconciler) getDynamicHostWaitTime() time.Duration { t := r.Config.GetInt("dynamicHostWaitTimeMin") if t > maxWaitTime { diff --git a/helm/db-controller/values.yaml b/helm/db-controller/values.yaml index b45a93f3..1f78f4a6 100644 --- a/helm/db-controller/values.yaml +++ b/helm/db-controller/values.yaml @@ -8,6 +8,8 @@ secrets: enabled: false env: local +realm: "us" +lifecycle: "dev" db: identifier: prefix: "{{ .Values.env }}" @@ -158,3 +160,8 @@ controllerConfig: enablePerfInsight: true # Possible values for enableCloudwatchLogsExport are all, none, postgresql and upgrade. enableCloudwatchLogsExport: "none" + system-functions: + ib_realm: "{{ tpl .Values.realm . }}" + ib_env: "{{ .Values.env }}" + ib_lifecycle: "{{ .Values.lifecycle }}" + diff --git a/pkg/dbclient/client.go b/pkg/dbclient/client.go index 5b40ccc1..3ae5fc98 100644 --- a/pkg/dbclient/client.go +++ b/pkg/dbclient/client.go @@ -163,6 +163,44 @@ func (pc *client) CreateDefaultExtentions(dbName string) error { return err } +func (pc *client) ManageSystemFunctions(dbName string, functions map[string]string) error { + db, err := pc.getDB(dbName) + if err != nil { + pc.log.Error(err, "could not connect to db", "database", dbName) + return err + } + pc.log.Info("ManageSystemFunctions - connected to " + dbName) + defer db.Close() + var currVal string + var create bool + for f, v := range functions { + create = false + err := db.QueryRow(fmt.Sprintf("SELECT %s()", f)).Scan(&currVal) + //check if error contains "does not exist" + if err != nil { + if strings.Contains(err.Error(), "does not exist") { + pc.log.Info("function does not exist - create it", "function", f) + create = true + } else { + return err + } + } else { + if currVal != v { + pc.log.Info("function value is not correct - update it", "function", f) + create = true + } + } + if create { + _, err = db.Exec(fmt.Sprintf("CREATE OR REPLACE FUNCTION %s() RETURNS text AS $$SELECT text '%s'$$ LANGUAGE sql IMMUTABLE PARALLEL SAFE;", f, v)) + if err != nil { + pc.log.Error(err, "could not create function", "database_name", dbName, "function", f, "value", v) + return err + } + } + } + return nil +} + func (pc *client) CreateGroup(dbName, rolename string) (bool, error) { start := time.Now() var exists bool diff --git a/pkg/dbclient/client_test.go b/pkg/dbclient/client_test.go index 3e496d9e..a21389b0 100644 --- a/pkg/dbclient/client_test.go +++ b/pkg/dbclient/client_test.go @@ -514,6 +514,38 @@ func TestPostgresClientOperations(t *testing.T) { if _, err := db.Exec("CREATE EXTENSION IF NOT EXISTS citext"); err != nil { t.Errorf("citext is not created: %s", err) } + t.Logf("create system functions") + functions := map[string]string{ + "ib_realm": "eu", + "ib_env": "box-3", + "ib_lifecycle": "dev", + } + err = pc.ManageSystemFunctions(tt.args.dbName, functions) + if (err != nil) != tt.wantErr { + t.Errorf("\t%s ManageSystemFunctions() error = %v, wantErr %v", failed, err, tt.wantErr) + } else { + t.Logf("\t%s create system functions passed", succeed) + } + t.Logf("re-create same system functions") + err = pc.ManageSystemFunctions(tt.args.dbName, functions) + if (err != nil) != tt.wantErr { + t.Errorf("\t%s re-create ManageSystemFunctions() error = %v, wantErr %v", failed, err, tt.wantErr) + } else { + t.Logf("\t%s create same system functions passed", succeed) + } + t.Logf("re-create different system functions") + functions = map[string]string{ + "ib_realm": "us", + "ib_env": "box-4", + "ib_lifecycle": "stage", + } + err = pc.ManageSystemFunctions(tt.args.dbName, functions) + if (err != nil) != tt.wantErr { + t.Errorf("\t%s re-create ManageSystemFunctions() error = %v, wantErr %v", failed, err, tt.wantErr) + } else { + t.Logf("\t%s create different system functions passed", succeed) + } + }) } } diff --git a/pkg/dbclient/interface.go b/pkg/dbclient/interface.go index 954b12e2..1dd86748 100644 --- a/pkg/dbclient/interface.go +++ b/pkg/dbclient/interface.go @@ -13,6 +13,7 @@ type Client interface { ManageReplicationRole(username string, enableReplicationRole bool) error ManageSuperUserRole(username string, enableSuperUser bool) error ManageCreateRole(username string, enableCreateRole bool) error + ManageSystemFunctions(dbName string, functions map[string]string) error DBCloser } From e2229d6e708f583ed15847fbacbbae5dd041ae2e Mon Sep 17 00:00:00 2001 From: bjeevan-ib Date: Wed, 25 Oct 2023 11:11:38 -0700 Subject: [PATCH 2/2] support system function during upgrade --- Makefile | 2 +- controllers/databaseclaim_controller.go | 6 ++- helm/db-controller/minikube.yaml | 1 + helm/db-controller/templates/configmap.yaml | 2 +- helm/db-controller/values.yaml | 9 +++-- pkg/dbclient/client.go | 45 +++++++++++++++++++-- pkg/pgctl/pgctl.go | 1 + 7 files changed, 57 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index e3e99853..acc28cd9 100644 --- a/Makefile +++ b/Makefile @@ -343,7 +343,7 @@ push-chart-crd: clean: rm -f *build.properties || true rm -f *.tgz || true - rm .push* .image* + rm .push* .image* || true deploy-crds: .id build-chart-crd ${HELM} upgrade --install --namespace $(cat .id) ${CRDS_CHART} --version $(CHART_VERSION) diff --git a/controllers/databaseclaim_controller.go b/controllers/databaseclaim_controller.go index 4f9d0098..67004f8d 100644 --- a/controllers/databaseclaim_controller.go +++ b/controllers/databaseclaim_controller.go @@ -609,6 +609,10 @@ func (r *DatabaseClaimReconciler) reconcileNewDB(ctx context.Context, if err != nil { return ctrl.Result{}, err } + err = dbClient.ManageSystemFunctions(GetDBName(dbClaim), r.getSystemFunctions()) + if err != nil { + return ctrl.Result{}, err + } return ctrl.Result{}, nil } @@ -1079,7 +1083,7 @@ func (r *DatabaseClaimReconciler) getDbSubnetGroupNameRef() string { } func (r *DatabaseClaimReconciler) getSystemFunctions() map[string]string { - return r.Config.GetStringMapString("system-functions") + return r.Config.GetStringMapString("systemFunctions") } func (r *DatabaseClaimReconciler) getDynamicHostWaitTime() time.Duration { diff --git a/helm/db-controller/minikube.yaml b/helm/db-controller/minikube.yaml index b8b64797..17043567 100644 --- a/helm/db-controller/minikube.yaml +++ b/helm/db-controller/minikube.yaml @@ -8,3 +8,4 @@ zapLogger: level: debug dbproxy: enabled: false +env: box-3 \ No newline at end of file diff --git a/helm/db-controller/templates/configmap.yaml b/helm/db-controller/templates/configmap.yaml index bc0b67a4..4c585adb 100644 --- a/helm/db-controller/templates/configmap.yaml +++ b/helm/db-controller/templates/configmap.yaml @@ -10,5 +10,5 @@ metadata: data: {{- with .Values.controllerConfig }} "config.yaml": |- - {{- toYaml . | trim | nindent 4 }} + {{- tpl (toYaml .) $ | trim | nindent 4 }} {{ end }} diff --git a/helm/db-controller/values.yaml b/helm/db-controller/values.yaml index 1f78f4a6..2bc50d91 100644 --- a/helm/db-controller/values.yaml +++ b/helm/db-controller/values.yaml @@ -8,7 +8,8 @@ secrets: enabled: false env: local -realm: "us" +ib: + realm: "us" lifecycle: "dev" db: identifier: @@ -160,8 +161,10 @@ controllerConfig: enablePerfInsight: true # Possible values for enableCloudwatchLogsExport are all, none, postgresql and upgrade. enableCloudwatchLogsExport: "none" - system-functions: - ib_realm: "{{ tpl .Values.realm . }}" + # system funtions are created as functions in the database. The prefix is used as the schema name. + # only "ib_" prefixed functions are supported at this time. + systemFunctions: + ib_realm: "{{ .Values.ib.realm }}" ib_env: "{{ .Values.env }}" ib_lifecycle: "{{ .Values.lifecycle }}" diff --git a/pkg/dbclient/client.go b/pkg/dbclient/client.go index 3ae5fc98..e165f2c2 100644 --- a/pkg/dbclient/client.go +++ b/pkg/dbclient/client.go @@ -171,11 +171,44 @@ func (pc *client) ManageSystemFunctions(dbName string, functions map[string]stri } pc.log.Info("ManageSystemFunctions - connected to " + dbName) defer db.Close() + //check if schema ib exists + var exists bool + err = db.QueryRow("SELECT EXISTS(SELECT 1 FROM information_schema.schemata WHERE schema_name = 'ib')").Scan(&exists) + if err != nil { + pc.log.Error(err, "could not query for schema ib") + return err + } + if !exists { + createSchema := ` + CREATE SCHEMA IF NOT EXISTS ib; + REVOKE ALL ON SCHEMA ib FROM PUBLIC; + GRANT USAGE ON SCHEMA ib TO PUBLIC; + REVOKE ALL ON ALL TABLES IN SCHEMA ib FROM PUBLIC ; + GRANT SELECT ON ALL TABLES IN SCHEMA ib TO PUBLIC; + ` + //create schema ib + _, err = db.Exec(createSchema) + if err != nil { + pc.log.Error(err, "could not create schema ib") + return err + } + } var currVal string var create bool + var schema string + sep := "_" // separator between schema and function name for f, v := range functions { create = false - err := db.QueryRow(fmt.Sprintf("SELECT %s()", f)).Scan(&currVal) + // split ib_lifecycle into schema and function name. eg. ib_life_cycle -> ib.life_cycle + f_parts := strings.Split(f, sep) + if len(f_parts) == 1 { + schema = "public" + } else { + schema = f_parts[0] + f = pq.QuoteIdentifier(strings.Join(f_parts[1:], sep)) + } + + err := db.QueryRow(fmt.Sprintf("SELECT %s.%s()", schema, f)).Scan(&currVal) //check if error contains "does not exist" if err != nil { if strings.Contains(err.Error(), "does not exist") { @@ -191,9 +224,15 @@ func (pc *client) ManageSystemFunctions(dbName string, functions map[string]stri } } if create { - _, err = db.Exec(fmt.Sprintf("CREATE OR REPLACE FUNCTION %s() RETURNS text AS $$SELECT text '%s'$$ LANGUAGE sql IMMUTABLE PARALLEL SAFE;", f, v)) + createFunction := ` + CREATE OR REPLACE FUNCTION %s.%s() + RETURNS text AS $$SELECT text '%s'$$ + LANGUAGE sql IMMUTABLE PARALLEL SAFE; + ` + _, err = db.Exec(fmt.Sprintf(createFunction, schema, f, v)) if err != nil { - pc.log.Error(err, "could not create function", "database_name", dbName, "function", f, "value", v) + pc.log.Error(err, "could not create function", "database_name", + dbName, "function", f, "value", v) return err } } diff --git a/pkg/pgctl/pgctl.go b/pkg/pgctl/pgctl.go index 7b12468a..2b59a713 100644 --- a/pkg/pgctl/pgctl.go +++ b/pkg/pgctl/pgctl.go @@ -315,6 +315,7 @@ func (s *copy_schema_state) Execute() (State, error) { "--no-subscriptions", "--no-privileges", "--no-owner", + "--exclude-schema=ib", }) dumpExec := dump.Exec(ExecOptions{StreamPrint: true})