From 2078d620fcd5c6622df35c18b582a63eff21fe43 Mon Sep 17 00:00:00 2001 From: Manan Gupta <35839558+GuptaManan100@users.noreply.github.com> Date: Thu, 28 Mar 2024 10:47:17 +0530 Subject: [PATCH] Add api end point to print the current database state in VTOrc (#15485) Signed-off-by: Manan Gupta --- go/test/endtoend/vtorc/api/api_test.go | 18 ++++++++++++++ go/vt/external/golib/sqlutils/sqlutils.go | 2 +- go/vt/vtorc/db/generate_base.go | 22 +++++++++++++++++ go/vt/vtorc/inst/instance_dao.go | 30 +++++++++++++++++++++++ go/vt/vtorc/inst/instance_dao_test.go | 16 ++++++++++++ go/vt/vtorc/server/api.go | 16 +++++++++++- 6 files changed, 102 insertions(+), 2 deletions(-) diff --git a/go/test/endtoend/vtorc/api/api_test.go b/go/test/endtoend/vtorc/api/api_test.go index 30e43dfc29a..05b757a90ac 100644 --- a/go/test/endtoend/vtorc/api/api_test.go +++ b/go/test/endtoend/vtorc/api/api_test.go @@ -93,6 +93,24 @@ func TestAPIEndpoints(t *testing.T) { return response != "null" }) + t.Run("Database State", func(t *testing.T) { + // Get database state + status, resp, err := utils.MakeAPICall(t, vtorc, "/api/database-state") + require.NoError(t, err) + assert.Equal(t, 200, status) + assert.Contains(t, resp, `"alias": "zone1-0000000101"`) + assert.Contains(t, resp, `{ + "TableName": "vitess_keyspace", + "Rows": [ + { + "durability_policy": "none", + "keyspace": "ks", + "keyspace_type": "0" + } + ] + },`) + }) + t.Run("Disable Recoveries API", func(t *testing.T) { // Disable recoveries of VTOrc status, resp, err := utils.MakeAPICall(t, vtorc, "/api/disable-global-recoveries") diff --git a/go/vt/external/golib/sqlutils/sqlutils.go b/go/vt/external/golib/sqlutils/sqlutils.go index c88af1176b1..ecf80649b0b 100644 --- a/go/vt/external/golib/sqlutils/sqlutils.go +++ b/go/vt/external/golib/sqlutils/sqlutils.go @@ -42,7 +42,7 @@ type RowMap map[string]CellData // CellData is the result of a single (atomic) column in a single row type CellData sql.NullString -func (this *CellData) MarshalJSON() ([]byte, error) { +func (this CellData) MarshalJSON() ([]byte, error) { if this.Valid { return json.Marshal(this.String) } else { diff --git a/go/vt/vtorc/db/generate_base.go b/go/vt/vtorc/db/generate_base.go index 73238802920..94daebbf7f0 100644 --- a/go/vt/vtorc/db/generate_base.go +++ b/go/vt/vtorc/db/generate_base.go @@ -16,6 +16,28 @@ package db +var TableNames = []string{ + "database_instance", + "audit", + "active_node", + "node_health", + "topology_recovery", + "database_instance_topology_history", + "candidate_database_instance", + "topology_failure_detection", + "blocked_topology_recovery", + "database_instance_last_analysis", + "database_instance_analysis_changelog", + "node_health_history", + "vtorc_db_deployments", + "global_recovery_disable", + "topology_recovery_steps", + "database_instance_stale_binlog_coordinates", + "vitess_tablet", + "vitess_keyspace", + "vitess_shard", +} + // vtorcBackend is a list of SQL statements required to build the vtorc backend var vtorcBackend = []string{ ` diff --git a/go/vt/vtorc/inst/instance_dao.go b/go/vt/vtorc/inst/instance_dao.go index 250d2bd6ba6..35b5d11bc95 100644 --- a/go/vt/vtorc/inst/instance_dao.go +++ b/go/vt/vtorc/inst/instance_dao.go @@ -17,6 +17,7 @@ package inst import ( + "encoding/json" "errors" "fmt" "regexp" @@ -1210,3 +1211,32 @@ func ExpireStaleInstanceBinlogCoordinates() error { } return ExecDBWriteFunc(writeFunc) } + +// GetDatabaseState takes the snapshot of the database and returns it. +func GetDatabaseState() (string, error) { + type tableState struct { + TableName string + Rows []sqlutils.RowMap + } + + var dbState []tableState + for _, tableName := range db.TableNames { + ts := tableState{ + TableName: tableName, + } + err := db.QueryVTOrc("select * from "+tableName, nil, func(rowMap sqlutils.RowMap) error { + ts.Rows = append(ts.Rows, rowMap) + return nil + }) + if err != nil { + return "", err + } + dbState = append(dbState, ts) + } + jsonData, err := json.MarshalIndent(dbState, "", "\t") + if err != nil { + return "", err + } + + return string(jsonData), nil +} diff --git a/go/vt/vtorc/inst/instance_dao_test.go b/go/vt/vtorc/inst/instance_dao_test.go index 549389f91fe..c6020ec52d8 100644 --- a/go/vt/vtorc/inst/instance_dao_test.go +++ b/go/vt/vtorc/inst/instance_dao_test.go @@ -746,3 +746,19 @@ func waitForCacheInitialization() { time.Sleep(100 * time.Millisecond) } } + +func TestGetDatabaseState(t *testing.T) { + // Clear the database after the test. The easiest way to do that is to run all the initialization commands again. + defer func() { + db.ClearVTOrcDatabase() + }() + + for _, query := range initialSQL { + _, err := db.ExecVTOrc(query) + require.NoError(t, err) + } + + ds, err := GetDatabaseState() + require.NoError(t, err) + require.Contains(t, ds, `"alias": "zone1-0000000112"`) +} diff --git a/go/vt/vtorc/server/api.go b/go/vt/vtorc/server/api.go index b0112e10add..60fdf226e95 100644 --- a/go/vt/vtorc/server/api.go +++ b/go/vt/vtorc/server/api.go @@ -45,6 +45,7 @@ const ( disableGlobalRecoveriesAPI = "/api/disable-global-recoveries" enableGlobalRecoveriesAPI = "/api/enable-global-recoveries" replicationAnalysisAPI = "/api/replication-analysis" + databaseStateAPI = "/api/database-state" healthAPI = "/debug/health" AggregatedDiscoveryMetricsAPI = "/api/aggregated-discovery-metrics" @@ -60,6 +61,7 @@ var ( disableGlobalRecoveriesAPI, enableGlobalRecoveriesAPI, replicationAnalysisAPI, + databaseStateAPI, healthAPI, AggregatedDiscoveryMetricsAPI, } @@ -86,6 +88,8 @@ func (v *vtorcAPI) ServeHTTP(response http.ResponseWriter, request *http.Request errantGTIDsAPIHandler(response, request) case replicationAnalysisAPI: replicationAnalysisAPIHandler(response, request) + case databaseStateAPI: + databaseStateAPIHandler(response) case AggregatedDiscoveryMetricsAPI: AggregatedDiscoveryMetricsAPIHandler(response, request) default: @@ -104,7 +108,7 @@ func getACLPermissionLevelForAPI(apiEndpoint string) string { return acl.ADMIN case replicationAnalysisAPI: return acl.MONITORING - case healthAPI: + case healthAPI, databaseStateAPI: return acl.MONITORING } return acl.ADMIN @@ -166,6 +170,16 @@ func errantGTIDsAPIHandler(response http.ResponseWriter, request *http.Request) returnAsJSON(response, http.StatusOK, instances) } +// databaseStateAPIHandler is the handler for the databaseStateAPI endpoint +func databaseStateAPIHandler(response http.ResponseWriter) { + ds, err := inst.GetDatabaseState() + if err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + writePlainTextResponse(response, ds, http.StatusOK) +} + // AggregatedDiscoveryMetricsAPIHandler is the handler for the discovery metrics endpoint func AggregatedDiscoveryMetricsAPIHandler(response http.ResponseWriter, request *http.Request) { // return metrics for last x seconds