github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/lorry/engines/wesql/manager.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package wesql
    21  
    22  import (
    23  	"context"
    24  	"database/sql"
    25  	"fmt"
    26  	"strings"
    27  
    28  	"github.com/pkg/errors"
    29  	ctrl "sigs.k8s.io/controller-runtime"
    30  
    31  	"github.com/1aal/kubeblocks/pkg/lorry/dcs"
    32  	"github.com/1aal/kubeblocks/pkg/lorry/engines"
    33  	"github.com/1aal/kubeblocks/pkg/lorry/engines/mysql"
    34  )
    35  
    36  const (
    37  	Role        = "ROLE"
    38  	CurrentRole = "CURRENT_ROLE"
    39  	Leader      = "Leader"
    40  )
    41  
    42  type Manager struct {
    43  	mysql.Manager
    44  }
    45  
    46  var _ engines.DBManager = &Manager{}
    47  
    48  func NewManager(properties engines.Properties) (engines.DBManager, error) {
    49  	logger := ctrl.Log.WithName("WeSQL")
    50  	_, err := NewConfig(properties)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	mysqlMgr, err := mysql.NewManager(properties)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	mgr := &Manager{
    61  		Manager: *mysqlMgr.(*mysql.Manager),
    62  	}
    63  
    64  	mgr.SetLogger(logger)
    65  	return mgr, nil
    66  }
    67  
    68  func (mgr *Manager) InitializeCluster(ctx context.Context, cluster *dcs.Cluster) error {
    69  	return nil
    70  }
    71  
    72  func (mgr *Manager) IsLeader(ctx context.Context, cluster *dcs.Cluster) (bool, error) {
    73  	role, err := mgr.GetReplicaRole(ctx, cluster)
    74  
    75  	if err != nil {
    76  		return false, err
    77  	}
    78  
    79  	if strings.EqualFold(role, Leader) {
    80  		return true, nil
    81  	}
    82  
    83  	return false, nil
    84  }
    85  
    86  func (mgr *Manager) IsLeaderMember(ctx context.Context, cluster *dcs.Cluster, member *dcs.Member) (bool, error) {
    87  	if member == nil {
    88  		return false, nil
    89  	}
    90  
    91  	leaderMember := mgr.GetLeaderMember(ctx, cluster)
    92  	if leaderMember == nil {
    93  		return false, nil
    94  	}
    95  
    96  	if leaderMember.Name != member.Name {
    97  		return false, nil
    98  	}
    99  
   100  	return true, nil
   101  }
   102  
   103  func (mgr *Manager) InitiateCluster(cluster *dcs.Cluster) error {
   104  	return nil
   105  }
   106  
   107  func (mgr *Manager) GetMemberAddrs(ctx context.Context, cluster *dcs.Cluster) []string {
   108  	addrs := make([]string, 0, 3)
   109  	clusterInfo := mgr.GetClusterInfo(ctx, cluster)
   110  	clusterInfo = strings.Split(clusterInfo, "@")[0]
   111  	for _, addr := range strings.Split(clusterInfo, ";") {
   112  		if !strings.Contains(addr, ":") {
   113  			continue
   114  		}
   115  		addrs = append(addrs, strings.Split(addr, "#")[0])
   116  	}
   117  
   118  	return addrs
   119  }
   120  
   121  func (mgr *Manager) GetAddrWithMemberName(ctx context.Context, cluster *dcs.Cluster, memberName string) string {
   122  	addrs := mgr.GetMemberAddrs(ctx, cluster)
   123  	for _, addr := range addrs {
   124  		if strings.HasPrefix(addr, memberName) {
   125  			return addr
   126  		}
   127  	}
   128  	return ""
   129  }
   130  
   131  func (mgr *Manager) IsCurrentMemberInCluster(ctx context.Context, cluster *dcs.Cluster) bool {
   132  	clusterInfo := mgr.GetClusterInfo(ctx, cluster)
   133  	return strings.Contains(clusterInfo, mgr.CurrentMemberName)
   134  }
   135  
   136  func (mgr *Manager) IsMemberLagging(context.Context, *dcs.Cluster, *dcs.Member) (bool, int64) {
   137  	return false, 0
   138  }
   139  
   140  func (mgr *Manager) Recover(context.Context) error {
   141  	return nil
   142  }
   143  
   144  func (mgr *Manager) JoinCurrentMemberToCluster(context.Context, *dcs.Cluster) error {
   145  	return nil
   146  }
   147  
   148  func (mgr *Manager) LeaveMemberFromCluster(ctx context.Context, cluster *dcs.Cluster, memberName string) error {
   149  	db, err := mgr.GetLeaderConn(ctx, cluster)
   150  	if err != nil {
   151  		mgr.Logger.Error(err, "Get leader conn failed")
   152  		return err
   153  	}
   154  	addr := mgr.GetAddrWithMemberName(ctx, cluster, memberName)
   155  	if addr == "" {
   156  		mgr.Logger.Info(fmt.Sprintf("member %s already deleted", memberName))
   157  		return nil
   158  	}
   159  
   160  	sql := fmt.Sprintf("call dbms_consensus.downgrade_follower('%s');"+
   161  		"call dbms_consensus.drop_learner('%s');", addr, addr)
   162  	_, err = db.ExecContext(ctx, sql)
   163  	if err != nil {
   164  		mgr.Logger.Error(err, "delete member from db cluster failed")
   165  		return errors.Wrapf(err, "error executing %s", sql)
   166  	}
   167  	return nil
   168  }
   169  
   170  func (mgr *Manager) IsClusterHealthy(ctx context.Context, cluster *dcs.Cluster) bool {
   171  	db, err := mgr.GetLeaderConn(ctx, cluster)
   172  	if err != nil {
   173  		mgr.Logger.Error(err, "Get leader conn failed")
   174  		return false
   175  	}
   176  	if db == nil {
   177  		return false
   178  	}
   179  
   180  	defer db.Close()
   181  	var leaderRecord mysql.RowMap
   182  	sql := "select * from information_schema.wesql_cluster_global;"
   183  	err = mysql.QueryRowsMap(db, sql, func(rMap mysql.RowMap) error {
   184  		if rMap.GetString(Role) == Leader {
   185  			leaderRecord = rMap
   186  		}
   187  		return nil
   188  	})
   189  	if err != nil {
   190  		mgr.Logger.Error(err, fmt.Sprintf("error executing %s", sql))
   191  		return false
   192  	}
   193  
   194  	if len(leaderRecord) > 0 {
   195  		return true
   196  	}
   197  	return false
   198  }
   199  
   200  // IsClusterInitialized is a method to check if cluster is initailized or not
   201  func (mgr *Manager) IsClusterInitialized(ctx context.Context, cluster *dcs.Cluster) (bool, error) {
   202  	clusterInfo := mgr.GetClusterInfo(ctx, nil)
   203  	if clusterInfo != "" {
   204  		return true, nil
   205  	}
   206  
   207  	return false, nil
   208  }
   209  
   210  func (mgr *Manager) GetClusterInfo(ctx context.Context, cluster *dcs.Cluster) string {
   211  	var db *sql.DB
   212  	var err error
   213  	if cluster != nil {
   214  		db, err = mgr.GetLeaderConn(ctx, cluster)
   215  		if err != nil {
   216  			mgr.Logger.Error(err, "Get leader conn failed")
   217  			return ""
   218  		}
   219  		if db != nil {
   220  			defer db.Close()
   221  		}
   222  	} else {
   223  		db = mgr.DB
   224  
   225  	}
   226  	var clusterID, clusterInfo string
   227  	err = db.QueryRowContext(ctx, "select cluster_id, cluster_info from mysql.consensus_info").
   228  		Scan(&clusterID, &clusterInfo)
   229  	if err != nil {
   230  		mgr.Logger.Error(err, "Cluster info query failed")
   231  	}
   232  	return clusterInfo
   233  }
   234  
   235  func (mgr *Manager) Promote(ctx context.Context, cluster *dcs.Cluster) error {
   236  	isLeader, _ := mgr.IsLeader(ctx, nil)
   237  	if isLeader {
   238  		return nil
   239  	}
   240  
   241  	db, err := mgr.GetLeaderConn(ctx, cluster)
   242  	if err != nil {
   243  		return errors.Wrap(err, "Get leader conn failed")
   244  	}
   245  	if db != nil {
   246  		defer db.Close()
   247  	}
   248  
   249  	currentMember := cluster.GetMemberWithName(mgr.GetCurrentMemberName())
   250  	addr := cluster.GetMemberAddr(*currentMember)
   251  	resp, err := db.Exec(fmt.Sprintf("call dbms_consensus.change_leader('%s:13306');", addr))
   252  	if err != nil {
   253  		mgr.Logger.Error(err, "promote err")
   254  		return err
   255  	}
   256  
   257  	mgr.Logger.Info("promote success", "resp", resp)
   258  	return nil
   259  }
   260  
   261  func (mgr *Manager) IsPromoted(ctx context.Context) bool {
   262  	isLeader, _ := mgr.IsLeader(ctx, nil)
   263  	return isLeader
   264  }
   265  
   266  func (mgr *Manager) Demote(context.Context) error {
   267  	return nil
   268  }
   269  
   270  func (mgr *Manager) Follow(ctx context.Context, cluster *dcs.Cluster) error {
   271  	return nil
   272  }
   273  
   274  func (mgr *Manager) GetHealthiestMember(cluster *dcs.Cluster, candidate string) *dcs.Member {
   275  	return nil
   276  }
   277  
   278  func (mgr *Manager) HasOtherHealthyLeader(ctx context.Context, cluster *dcs.Cluster) *dcs.Member {
   279  	clusterLocalInfo, err := mgr.GetClusterLocalInfo(ctx)
   280  	if err != nil || clusterLocalInfo == nil {
   281  		mgr.Logger.Error(err, "Get cluster local info failed")
   282  		return nil
   283  	}
   284  
   285  	if clusterLocalInfo.GetString(Role) == Leader {
   286  		// I am the leader, just return nil
   287  		return nil
   288  	}
   289  
   290  	leaderAddr := clusterLocalInfo.GetString(CurrentRole)
   291  	if leaderAddr == "" {
   292  		return nil
   293  	}
   294  	leaderParts := strings.Split(leaderAddr, ".")
   295  	if len(leaderParts) > 0 {
   296  		return cluster.GetMemberWithName(leaderParts[0])
   297  	}
   298  
   299  	return nil
   300  }
   301  
   302  // HasOtherHealthyMembers checks if there are any healthy members, excluding the leader
   303  func (mgr *Manager) HasOtherHealthyMembers(ctx context.Context, cluster *dcs.Cluster, leader string) []*dcs.Member {
   304  	members := make([]*dcs.Member, 0)
   305  	for _, member := range cluster.Members {
   306  		if member.Name == leader {
   307  			continue
   308  		}
   309  		if !mgr.IsMemberHealthy(ctx, cluster, &member) {
   310  			continue
   311  		}
   312  		members = append(members, &member)
   313  	}
   314  
   315  	return members
   316  }