| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| package mysql |
| |
| import ( |
| _ "crypto/tls" |
| "database/sql/driver" |
| "errors" |
| "fmt" |
| "regexp" |
| "strconv" |
| "testing" |
| |
| "github.com/DATA-DOG/go-sqlmock" |
| "github.com/jmoiron/sqlx" |
| ) |
| |
| func Test_ShowFrontends(t *testing.T) { |
| mysql_db, mock, err := sqlmock.New() |
| if err != nil { |
| t.Errorf("sqlmock new failed %s", err.Error()) |
| } |
| |
| columns := []string{"Name", "Host", "EditLogPort", "HttpPort", "QueryPort", "RpcPort", "ArrowFlightSqlPort", "Role", "IsMaster", |
| "ClusterId", "Join", "Alive", "ReplayedJournalId", "LastStartTime", "LastHeartbeat", "IsHelper", "ErrMsg", "Version", "CurrentConnected", "LiveSince", "FutureUnknownColumn"} |
| values := []driver.Value{"fe_36d7bccc_d358_4dfd_ad4c_6e988f94f12d", "doriscluster-sample-fe-0.doriscluster-sample-fe-internal.default.svc.cluster.local", 9010, 8030, 9030, 9020, -1, "FOLLOWER", true, "1807668748", true, true, "15443", "2024-08-21 10:04:29", |
| "2024-08-22 07:29:55", true, "", "doris-2.1.5-rc02-d5a02e095d", "Yes", "2024-08-21 10:04:29", "ignored"} |
| mock.ExpectQuery("show frontends").WillReturnRows(sqlmock.NewRows(columns).AddRows(values)) |
| dorisdb := sqlx.NewDb(mysql_db, "mysql") |
| db := &DB{ |
| DB: dorisdb, |
| } |
| defer db.Close() |
| fts, err := db.ShowFrontends() |
| if err != nil { |
| t.Errorf("show frontends failed, %s", err.Error()) |
| } |
| if len(fts) != 1 { |
| t.Fatalf("show frontends failed, expected one frontend, got %d", len(fts)) |
| } |
| if fts[0].Host != "doriscluster-sample-fe-0.doriscluster-sample-fe-internal.default.svc.cluster.local" || fts[0].Role != "FOLLOWER" { |
| t.Errorf("show frontends failed, known fields were not mapped: %+v", fts[0]) |
| } |
| } |
| |
| func Test_ShowBackends(t *testing.T) { |
| columns := []string{"BackendId", "Host", "HeartbeatPort", "BePort", "HttpPort", "BrpcPort", "ArrowFlightSqlPort", "LastStartTime", |
| "LastHeartbeat", "Alive", "SystemDecommissioned", "TabletNum", "DataUsedCapacity", "TrashUsedCapacity", "AvailCapacity", "TotalCapacity", "UsedPct", "MaxDiskUsedPct", |
| "RemoteUsedCapacity", "Tag", "ErrMsg", "Version", "Status", "HeartbeatFailureCounter", "NodeRole", "LiveSince", "FutureUnknownColumn"} |
| values := []driver.Value{"10009", "doriscluster-sample-be-0.doriscluster-sample-be-internal.default.svc.cluster.local", 9050, 9060, 8040, 8060, -1, "2024-08-21 10:05:37", |
| "2024-08-22 08:29:46", true, false, 24, "0.000", "0.000", "74.619 GB", "439.037 GB", "83.00 %", "83.00 %", "0.000", |
| "{\"location\" : \"default\"}", "", "doris-2.1.5-rc02-d5a02e095d", "{\"lastSuccessReportTabletsTime\":\"2024-08-22 08:29:09\",\"lastStreamLoadTime\":-1,\"isQueryDisabled\":false,\"isLoadDisabled\":false}", |
| 0, "mix", "2024-08-21 10:05:37", "ignored"} |
| mysql_db, mock, err := sqlmock.New() |
| if err != nil { |
| t.Errorf("sqlmock new failed %s", err.Error()) |
| } |
| mock.ExpectQuery("show backends").WillReturnRows(sqlmock.NewRows(columns).AddRows(values)) |
| dorisdb := sqlx.NewDb(mysql_db, "mysql") |
| db := &DB{ |
| DB: dorisdb, |
| } |
| defer db.Close() |
| |
| bds, err := db.ShowBackends() |
| if err != nil { |
| t.Errorf("show backends failed, %s", err.Error()) |
| } |
| if len(bds) != 1 { |
| t.Fatalf("show backends failed, expected one backend, got %d", len(bds)) |
| } |
| if bds[0].BackendID != "10009" || bds[0].NodeRole != "mix" { |
| t.Errorf("show backends failed, known fields were not mapped: %+v", bds[0]) |
| } |
| } |
| |
| func TestDB_GetBackendsByCGName(t *testing.T) { |
| columns := []string{"BackendId", "Host", "HeartbeatPort", "BePort", "HttpPort", "BrpcPort", "ArrowFlightSqlPort", "LastStartTime", |
| "LastHeartbeat", "Alive", "SystemDecommissioned", "TabletNum", "DataUsedCapacity", "TrashUsedCapacity", "AvailCapacity", "TotalCapacity", "UsedPct", "MaxDiskUsedPct", |
| "RemoteUsedCapacity", "Tag", "ErrMsg", "Version", "Status", "HeartbeatFailureCounter", "NodeRole"} |
| values := []driver.Value{"10009", "doriscluster-sample-be-0.doriscluster-sample-be-internal.default.svc.cluster.local", 9050, 9060, 8040, 8060, -1, "2024-08-21 10:05:37", |
| "2024-08-22 08:29:46", true, false, 24, "0.000", "0.000", "74.619 GB", "439.037 GB", "83.00 %", "83.00 %", "0.000", |
| "{\"location\" : \"default\",\"compute_group_name\":\"test\"}", "", "doris-2.1.5-rc02-d5a02e095d", "{\"lastSuccessReportTabletsTime\":\"2024-08-22 08:29:09\",\"lastStreamLoadTime\":-1,\"isQueryDisabled\":false,\"isLoadDisabled\":false}", |
| 0, "mix"} |
| mysql_db, mock, err := sqlmock.New() |
| if err != nil { |
| t.Errorf("sqlmock new failed %s", err.Error()) |
| } |
| mock.ExpectQuery("show backends").WillReturnRows(sqlmock.NewRows(columns).AddRows(values)) |
| dorisdb := sqlx.NewDb(mysql_db, "mysql") |
| db := &DB{ |
| DB: dorisdb, |
| } |
| defer db.Close() |
| |
| db.GetBackendsByComputeGroupId("test") |
| } |
| |
| func Test_DecommissionBE(t *testing.T) { |
| version := "doris-2.1.5-rc02-d5a02e095d" |
| startTime := "2024-08-21 10:05:37" |
| heartbeat := "2024-08-22 08:29:46" |
| values := []*Backend{{BackendID: "10009", Host: "doriscluster-sample-be-0.doriscluster-sample-be-internal.default.svc.cluster.local", HeartbeatPort: 9050, BePort: 9060, HttpPort: 8040, BrpcPort: 8060, ArrowFlightSqlPort: -1, LastStartTime: &startTime, |
| LastHeartbeat: &heartbeat, Alive: true, TabletNum: 24, DataUsedCapacity: "0.000", TrashUsedCapacity: "0.000", AvailCapacity: "74.619 GB", TotalCapacity: "439.037 GB", UsedPct: "83.00 %", MaxDiskUsedPct: "83.00 %", |
| RemoteUsedCapacity: "0.000", ErrMsg: "", Version: &version, Status: "{\"lastSuccessReportTabletsTime\":\"2024-08-22 08:29:09\",\"lastStreamLoadTime\":-1,\"isQueryDisabled\":false,\"isLoadDisabled\":false}", HeartbeatFailureCounter: 0, NodeRole: "mix"}} |
| values2 := []*Backend{{BackendID: "10009", Host: "doriscluster-sample-be-0.doriscluster-sample-be-internal.default.svc.cluster.local", HeartbeatPort: 9050, BePort: 9060, HttpPort: 8040, BrpcPort: 8060, ArrowFlightSqlPort: -1, LastStartTime: &startTime, |
| LastHeartbeat: &heartbeat, Alive: true, TabletNum: 24, DataUsedCapacity: "0.000", TrashUsedCapacity: "0.000", AvailCapacity: "74.619 GB", TotalCapacity: "439.037 GB", UsedPct: "83.00 %", MaxDiskUsedPct: "83.00 %", |
| RemoteUsedCapacity: "0.000", ErrMsg: "", Version: &version, Status: "{\"lastSuccessReportTabletsTime\":\"2024-08-22 08:29:09\",\"lastStreamLoadTime\":-1,\"isQueryDisabled\":false,\"isLoadDisabled\":false}", HeartbeatFailureCounter: 0, NodeRole: "mix"}, |
| {BackendID: "10010", Host: "doriscluster-sample-be-1.doriscluster-sample-be-internal.default.svc.cluster.local", HeartbeatPort: 9050, BePort: 9060, HttpPort: 8040, BrpcPort: 8060, ArrowFlightSqlPort: -1, LastStartTime: &startTime, |
| LastHeartbeat: &heartbeat, Alive: true, TabletNum: 24, DataUsedCapacity: "0.000", TrashUsedCapacity: "0.000", AvailCapacity: "74.619 GB", TotalCapacity: "439.037 GB", UsedPct: "83.00 %", MaxDiskUsedPct: "83.00 %", |
| RemoteUsedCapacity: "0.000", ErrMsg: "", Version: &version, Status: "{\"lastSuccessReportTabletsTime\":\"2024-08-22 08:29:09\",\"lastStreamLoadTime\":-1,\"isQueryDisabled\":false,\"isLoadDisabled\":false}", HeartbeatFailureCounter: 0, NodeRole: "mix"}} |
| tests := [][]*Backend{ |
| {}, |
| values, |
| values2, |
| } |
| mysql_db, mock, err := sqlmock.New() |
| if err != nil { |
| t.Errorf("sqlmock new failed %s", err.Error()) |
| } |
| mock.ExpectExec("ALTER SYSTEM DECOMMISSION BACKEND").WillReturnResult(sqlmock.NewResult(1, 1)) |
| mock.ExpectExec(`ALTER SYSTEM DECOMMISSION BACKEND "doriscluster-sample-be-0.doriscluster-sample-be-internal.default.svc.cluster.local:9050","doriscluster-sample-be-1.doriscluster-sample-be-internal.default.svc.cluster.local:9050"`).WillReturnResult(sqlmock.NewResult(1, 2)) |
| dorisdb := sqlx.NewDb(mysql_db, "mysql") |
| db := &DB{ |
| DB: dorisdb, |
| } |
| defer db.Close() |
| for i, test := range tests { |
| |
| t.Run("test"+strconv.Itoa(i), func(t *testing.T) { |
| err = db.DecommissionBE(test) |
| if err != nil { |
| t.Errorf("test decommission failed, err=%s", err.Error()) |
| } |
| }) |
| } |
| } |
| |
| func Test_DropObserver(t *testing.T) { |
| values := []*Frontend{ |
| {Host: "doriscluster-sample-fe-4.doriscluster-sample-fe-internal.default.svc.cluster.local", EditLogPort: 9010}, |
| {Host: "doriscluster-sample-fe-3.doriscluster-sample-fe-internal.default.svc.cluster.local", EditLogPort: 9010}, |
| } |
| |
| tests := [][]*Frontend{ |
| {}, |
| values, |
| } |
| |
| mysql_db, mock, err := sqlmock.New() |
| if err != nil { |
| t.Errorf("sqlmock new failed %s", err.Error()) |
| } |
| mock.ExpectExec(regexp.QuoteMeta("ALTER SYSTEM DROP OBSERVER \"doriscluster-sample-fe-4.doriscluster-sample-fe-internal.default.svc.cluster.local:9010\";")). |
| WillReturnResult(sqlmock.NewResult(1, 1)) |
| mock.ExpectExec(regexp.QuoteMeta("ALTER SYSTEM DROP OBSERVER \"doriscluster-sample-fe-3.doriscluster-sample-fe-internal.default.svc.cluster.local:9010\";")). |
| WillReturnResult(sqlmock.NewResult(1, 1)) |
| dorisdb := sqlx.NewDb(mysql_db, "mysql") |
| db := &DB{ |
| DB: dorisdb, |
| } |
| defer db.Close() |
| |
| for i, test := range tests { |
| t.Run("test"+strconv.Itoa(i), func(t *testing.T) { |
| err = db.DropObserver(test) |
| if err != nil { |
| t.Errorf("test decommission failed, err=%s", err.Error()) |
| } |
| }) |
| } |
| if err := mock.ExpectationsWereMet(); err != nil { |
| t.Fatalf("drop observer expectations were not met: %s", err) |
| } |
| } |
| |
| func Test_GetObservers(t *testing.T) { |
| mysql_db, mock, err := sqlmock.New() |
| if err != nil { |
| t.Errorf("sqlmock new failed %s", err.Error()) |
| } |
| |
| columns := []string{"Name", "Host", "EditLogPort", "HttpPort", "QueryPort", "RpcPort", "ArrowFlightSqlPort", "Role", "IsMaster", |
| "ClusterId", "Join", "Alive", "ReplayedJournalId", "LastStartTime", "LastHeartbeat", "IsHelper", "ErrMsg", "Version", "CurrentConnected"} |
| values := []driver.Value{"fe_36d7bccc_d358_4dfd_ad4c_6e988f94f12d", "doriscluster-sample-fe-0.doriscluster-sample-fe-internal.default.svc.cluster.local", 9010, 8030, 9030, 9020, -1, "OBSERVER", true, "1807668748", true, true, "15443", "2024-08-21 10:04:29", |
| "2024-08-22 07:29:55", true, "", "doris-2.1.5-rc02-d5a02e095d", "Yes"} |
| mock.ExpectQuery("show frontends").WillReturnRows(sqlmock.NewRows(columns).AddRows(values)) |
| dorisdb := sqlx.NewDb(mysql_db, "mysql") |
| db := &DB{ |
| DB: dorisdb, |
| } |
| defer db.Close() |
| |
| fts, err := db.GetObservers() |
| if err != nil { |
| t.Errorf("get observers failed, err=%s", err.Error()) |
| } |
| if len(fts) != 1 { |
| t.Errorf("get observers failed, not observer") |
| } |
| } |
| |
| func Test_GetFollowers(t *testing.T) { |
| mysql_db, mock, err := sqlmock.New() |
| if err != nil { |
| t.Errorf("sqlmock new failed %s", err.Error()) |
| } |
| |
| columns := []string{"Name", "Host", "EditLogPort", "HttpPort", "QueryPort", "RpcPort", "ArrowFlightSqlPort", "Role", "IsMaster", |
| "ClusterId", "Join", "Alive", "ReplayedJournalId", "LastStartTime", "LastHeartbeat", "IsHelper", "ErrMsg", "Version", "CurrentConnected"} |
| values := []driver.Value{"fe_36d7bccc_d358_4dfd_ad4c_6e988f94f12d", "doriscluster-sample-fe-0.doriscluster-sample-fe-internal.default.svc.cluster.local", 9010, 8030, 9030, 9020, -1, "FOLLOWER", true, "1807668748", true, true, "15443", "2024-08-21 10:04:29", |
| "2024-08-22 07:29:55", true, "", "doris-2.1.5-rc02-d5a02e095d", "Yes"} |
| mock.ExpectQuery("show frontends").WillReturnRows(sqlmock.NewRows(columns).AddRows(values)) |
| dorisdb := sqlx.NewDb(mysql_db, "mysql") |
| db := &DB{ |
| DB: dorisdb, |
| } |
| defer db.Close() |
| |
| _, fts, err := db.GetFollowers() |
| if err != nil { |
| t.Errorf("get observers failed, err=%s", err.Error()) |
| } |
| if len(fts) != 1 { |
| t.Errorf("get observers failed, not observer") |
| } |
| } |
| |
| func Test_DropBE(t *testing.T) { |
| mysqlDB, mock, err := sqlmock.New() |
| if err != nil { |
| t.Errorf("sqlmock new failed %s", err.Error()) |
| } |
| db := &DB{ |
| DB: sqlx.NewDb(mysqlDB, "mysql"), |
| } |
| defer db.Close() |
| |
| nodes := []*Backend{ |
| {Host: "test", HeartbeatPort: 9050}, |
| {Host: "test1", HeartbeatPort: 9050}, |
| } |
| for _, node := range nodes { |
| query := regexp.QuoteMeta(fmt.Sprintf(`ALTER SYSTEM DROPP BACKEND "%s:%d";`, node.Host, node.HeartbeatPort)) |
| mock.ExpectExec(query).WillReturnResult(sqlmock.NewResult(1, 1)) |
| } |
| if err := db.DropBE(nodes); err != nil { |
| t.Fatalf("drop backends failed: %v", err) |
| } |
| if err := db.DropBE(nil); err != nil { |
| t.Fatalf("drop empty backends failed: %v", err) |
| } |
| if err := mock.ExpectationsWereMet(); err != nil { |
| t.Fatalf("unmet sql expectations: %v", err) |
| } |
| } |
| |
| func TestDropBEReturnsUnexpectedError(t *testing.T) { |
| mysqlDB, mock, err := sqlmock.New() |
| if err != nil { |
| t.Fatalf("sqlmock new failed: %v", err) |
| } |
| db := &DB{DB: sqlx.NewDb(mysqlDB, "mysql")} |
| defer db.Close() |
| |
| query := regexp.QuoteMeta(`ALTER SYSTEM DROPP BACKEND "test:9050";`) |
| mock.ExpectExec(query).WillReturnError(errors.New("access denied")) |
| |
| if err := db.DropBE([]*Backend{{Host: "test", HeartbeatPort: 9050}}); err == nil { |
| t.Fatal("expected unexpected DropBE error to be returned") |
| } |
| if err := mock.ExpectationsWereMet(); err != nil { |
| t.Fatalf("unmet sql expectations: %v", err) |
| } |
| } |