github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/lorry/engines/mongodb/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 mongodb
    21  
    22  import (
    23  	"context"
    24  	"encoding/json"
    25  	"fmt"
    26  	"math/rand"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/pkg/errors"
    31  	"go.mongodb.org/mongo-driver/bson"
    32  	"go.mongodb.org/mongo-driver/mongo"
    33  	"go.mongodb.org/mongo-driver/mongo/options"
    34  	"go.mongodb.org/mongo-driver/mongo/readpref"
    35  	"go.mongodb.org/mongo-driver/mongo/writeconcern"
    36  	ctrl "sigs.k8s.io/controller-runtime"
    37  
    38  	"github.com/1aal/kubeblocks/pkg/lorry/dcs"
    39  	"github.com/1aal/kubeblocks/pkg/lorry/engines"
    40  )
    41  
    42  const (
    43  	PrimaryPriority   = 2
    44  	SecondaryPriority = 1
    45  
    46  	ServiceType = "mongodb"
    47  )
    48  
    49  type Manager struct {
    50  	engines.DBManagerBase
    51  	Client   *mongo.Client
    52  	Database *mongo.Database
    53  }
    54  
    55  var Mgr *Manager
    56  var _ engines.DBManager = &Manager{}
    57  
    58  func NewManager(properties engines.Properties) (engines.DBManager, error) {
    59  	ctx := context.Background()
    60  	logger := ctrl.Log.WithName("MongoDB")
    61  	config, err := NewConfig(properties)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	opts := options.Client().
    67  		SetHosts(config.hosts).
    68  		SetReplicaSet(config.replSetName).
    69  		SetAuth(options.Credential{
    70  			Password: config.password,
    71  			Username: config.username,
    72  		}).
    73  		SetWriteConcern(writeconcern.New(writeconcern.WMajority(), writeconcern.J(true))).
    74  		SetReadPreference(readpref.Primary()).
    75  		SetDirect(config.direct)
    76  
    77  	client, err := mongo.Connect(ctx, opts)
    78  	if err != nil {
    79  		return nil, errors.Wrap(err, "connect to mongodb")
    80  	}
    81  
    82  	defer func() {
    83  		if err != nil {
    84  			derr := client.Disconnect(ctx)
    85  			if derr != nil {
    86  				logger.Error(err, "failed to disconnect")
    87  			}
    88  		}
    89  	}()
    90  
    91  	managerBase, err := engines.NewDBManagerBase(logger)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	Mgr = &Manager{
    97  		DBManagerBase: *managerBase,
    98  		Client:        client,
    99  		Database:      client.Database(config.databaseName),
   100  	}
   101  
   102  	return Mgr, nil
   103  }
   104  
   105  func (mgr *Manager) InitializeCluster(ctx context.Context, cluster *dcs.Cluster) error {
   106  	return mgr.InitiateReplSet(ctx, cluster)
   107  }
   108  
   109  // InitiateReplSet is a method to create MongoDB cluster
   110  func (mgr *Manager) InitiateReplSet(ctx context.Context, cluster *dcs.Cluster) error {
   111  	configMembers := make([]ConfigMember, len(cluster.Members))
   112  
   113  	for i, member := range cluster.Members {
   114  		configMembers[i].ID = i
   115  		configMembers[i].Host = cluster.GetMemberAddrWithPort(member)
   116  		if strings.HasPrefix(member.Name, mgr.CurrentMemberName) {
   117  			configMembers[i].Priority = PrimaryPriority
   118  		} else {
   119  			configMembers[i].Priority = SecondaryPriority
   120  		}
   121  	}
   122  
   123  	config := RSConfig{
   124  		ID:      mgr.ClusterCompName,
   125  		Members: configMembers,
   126  	}
   127  	client, err := NewLocalUnauthClient(ctx)
   128  	if err != nil {
   129  		mgr.Logger.Error(err, "Get local unauth client failed")
   130  		return err
   131  	}
   132  	defer client.Disconnect(context.TODO()) //nolint:errcheck
   133  
   134  	configJSON, _ := json.Marshal(config)
   135  	mgr.Logger.Info(fmt.Sprintf("Initial Replset Config: %s", string(configJSON)))
   136  	response := client.Database("admin").RunCommand(ctx, bson.M{"replSetInitiate": config})
   137  	if response.Err() != nil {
   138  		return response.Err()
   139  	}
   140  	return nil
   141  }
   142  
   143  // IsClusterInitialized is a method to check if cluster is initailized or not
   144  func (mgr *Manager) IsClusterInitialized(ctx context.Context, cluster *dcs.Cluster) (bool, error) {
   145  	client, err := mgr.GetReplSetClient(ctx, cluster)
   146  	if err != nil {
   147  		mgr.Logger.Info("Get leader client failed", "error", err)
   148  		return false, err
   149  	}
   150  	defer client.Disconnect(ctx) //nolint:errcheck
   151  
   152  	ctx1, cancel := context.WithTimeout(ctx, 1000*time.Millisecond)
   153  	defer cancel()
   154  	rsStatus, err := GetReplSetStatus(ctx1, client)
   155  	if rsStatus != nil {
   156  		return rsStatus.Set != "", nil
   157  	}
   158  	mgr.Logger.Info("Get replSet status failed", "error", err)
   159  
   160  	if !mgr.IsFirstMember() {
   161  		return false, nil
   162  	}
   163  
   164  	client, err = NewLocalUnauthClient(ctx)
   165  	if err != nil {
   166  		mgr.Logger.Info("Get local unauth client failed", "error", err)
   167  		return false, err
   168  	}
   169  	defer client.Disconnect(ctx) //nolint:errcheck
   170  
   171  	rsStatus, err = GetReplSetStatus(ctx, client)
   172  	if rsStatus != nil {
   173  		return rsStatus.Set != "", nil
   174  	}
   175  
   176  	err = errors.Cause(err)
   177  	if cmdErr, ok := err.(mongo.CommandError); ok && cmdErr.Name == "NotYetInitialized" {
   178  		return false, nil
   179  	}
   180  	mgr.Logger.Info("Get replSet status with local unauth client failed", "error", err)
   181  
   182  	rsStatus, err = mgr.GetReplSetStatus(ctx)
   183  	if rsStatus != nil {
   184  		return rsStatus.Set != "", nil
   185  	}
   186  	if err != nil {
   187  		mgr.Logger.Info("Get replSet status with local auth client failed", "error", err)
   188  		return false, err
   189  	}
   190  
   191  	mgr.Logger.Info("Get replSet status failed", "error", err)
   192  	return false, err
   193  }
   194  
   195  func (mgr *Manager) IsRootCreated(ctx context.Context) (bool, error) {
   196  	if !mgr.IsFirstMember() {
   197  		return true, nil
   198  	}
   199  
   200  	client, err := NewLocalUnauthClient(ctx)
   201  	if err != nil {
   202  		mgr.Logger.Info("Get local unauth client failed", "error", err)
   203  		return false, err
   204  	}
   205  	defer client.Disconnect(ctx) //nolint:errcheck
   206  
   207  	_, err = GetReplSetStatus(ctx, client)
   208  	if err == nil {
   209  		return false, nil
   210  	}
   211  	err = errors.Cause(err)
   212  	if cmdErr, ok := err.(mongo.CommandError); ok && cmdErr.Name == "Unauthorized" {
   213  		return true, nil
   214  	}
   215  
   216  	mgr.Logger.Info("Get replSet status with local unauth client failed", "error", err)
   217  
   218  	_, err = mgr.GetReplSetStatus(ctx)
   219  	if err == nil {
   220  		return true, nil
   221  	}
   222  
   223  	mgr.Logger.Info("Get replSet status with local auth client failed", "error", err)
   224  	return false, err
   225  
   226  }
   227  
   228  func (mgr *Manager) CreateRoot(ctx context.Context) error {
   229  	if !mgr.IsFirstMember() {
   230  		return nil
   231  	}
   232  
   233  	client, err := NewLocalUnauthClient(ctx)
   234  	if err != nil {
   235  		mgr.Logger.Info("Get local unauth client failed", "error", err)
   236  		return err
   237  	}
   238  	defer client.Disconnect(ctx) //nolint:errcheck
   239  
   240  	role := map[string]interface{}{
   241  		"role": "root",
   242  		"db":   "admin",
   243  	}
   244  
   245  	mgr.Logger.Info(fmt.Sprintf("Create user: %s, passwd: %s, roles: %v", config.username, config.password, role))
   246  	err = CreateUser(ctx, client, config.username, config.password, role)
   247  	if err != nil {
   248  		mgr.Logger.Info("Create Root failed", "error", err)
   249  		return err
   250  	}
   251  
   252  	return nil
   253  }
   254  
   255  func (mgr *Manager) IsRunning() bool {
   256  	// ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
   257  	// defer cancel()
   258  
   259  	// err := mgr.Client.Ping(ctx, readpref.Nearest())
   260  	// if err != nil {
   261  	// 	mgr.Logger.Infof("DB is not ready: %v", err)
   262  	// 	return false
   263  	// }
   264  	return true
   265  }
   266  
   267  func (mgr *Manager) IsDBStartupReady() bool {
   268  	if mgr.DBStartupReady {
   269  		return true
   270  	}
   271  	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
   272  	defer cancel()
   273  
   274  	err := mgr.Client.Ping(ctx, readpref.Primary())
   275  	if err != nil {
   276  		mgr.Logger.Info("DB is not ready", "error", err)
   277  		return false
   278  	}
   279  	mgr.DBStartupReady = true
   280  	mgr.Logger.Info("DB startup ready")
   281  	return true
   282  }
   283  
   284  func (mgr *Manager) GetMemberState(ctx context.Context) (string, error) {
   285  	status, err := mgr.GetReplSetStatus(ctx)
   286  	if err != nil {
   287  		mgr.Logger.Error(err, "rs.status() error")
   288  		return "", err
   289  	}
   290  
   291  	self := status.GetSelf()
   292  	if self == nil {
   293  		return "", nil
   294  	}
   295  	return strings.ToLower(self.StateStr), nil
   296  }
   297  
   298  func (mgr *Manager) GetReplSetStatus(ctx context.Context) (*ReplSetStatus, error) {
   299  	return GetReplSetStatus(ctx, mgr.Client)
   300  }
   301  
   302  func (mgr *Manager) IsLeaderMember(ctx context.Context, cluster *dcs.Cluster, dcsMember *dcs.Member) (bool, error) {
   303  	status, err := mgr.GetReplSetStatus(ctx)
   304  	if err != nil {
   305  		mgr.Logger.Error(err, "rs.status() error")
   306  		return false, err
   307  	}
   308  	for _, member := range status.Members {
   309  		if strings.HasPrefix(member.Name, dcsMember.Name) {
   310  			if member.StateStr == "PRIMARY" {
   311  				return true, nil
   312  			}
   313  			break
   314  		}
   315  	}
   316  	return false, nil
   317  }
   318  
   319  func (mgr *Manager) IsLeader(ctx context.Context, cluster *dcs.Cluster) (bool, error) {
   320  	cur := mgr.Client.Database("admin").RunCommand(ctx, bson.D{{Key: "isMaster", Value: 1}})
   321  	if cur.Err() != nil {
   322  		return false, errors.Wrap(cur.Err(), "run isMaster")
   323  	}
   324  
   325  	resp := IsMasterResp{}
   326  	if err := cur.Decode(&resp); err != nil {
   327  		return false, errors.Wrap(err, "decode isMaster response")
   328  	}
   329  
   330  	if resp.OK != 1 {
   331  		return false, errors.Errorf("mongo says: %s", resp.Errmsg)
   332  	}
   333  
   334  	return resp.IsMaster, nil
   335  }
   336  
   337  func (mgr *Manager) GetReplSetConfig(ctx context.Context) (*RSConfig, error) {
   338  	return GetReplSetConfig(ctx, mgr.Client)
   339  }
   340  
   341  func (mgr *Manager) GetMemberAddrs(ctx context.Context, cluster *dcs.Cluster) []string {
   342  	client, err := mgr.GetReplSetClient(ctx, cluster)
   343  	if err != nil {
   344  		mgr.Logger.Error(err, "Get replSet client failed")
   345  		return nil
   346  	}
   347  	defer client.Disconnect(ctx) //nolint:errcheck
   348  
   349  	rsConfig, err := GetReplSetConfig(ctx, client)
   350  	if rsConfig == nil {
   351  		mgr.Logger.Error(err, "Get replSet config failed")
   352  		return nil
   353  	}
   354  
   355  	return mgr.GetMemberAddrsFromRSConfig(rsConfig)
   356  }
   357  
   358  func (mgr *Manager) GetMemberAddrsFromRSConfig(rsConfig *RSConfig) []string {
   359  	if rsConfig == nil {
   360  		return []string{}
   361  	}
   362  
   363  	hosts := make([]string, len(rsConfig.Members))
   364  	for i, member := range rsConfig.Members {
   365  		hosts[i] = member.Host
   366  	}
   367  	return hosts
   368  }
   369  
   370  func (mgr *Manager) GetReplSetClient(ctx context.Context, cluster *dcs.Cluster) (*mongo.Client, error) {
   371  	hosts := cluster.GetMemberAddrs()
   372  	return NewReplSetClient(ctx, hosts)
   373  }
   374  
   375  func (mgr *Manager) GetLeaderClient(ctx context.Context, cluster *dcs.Cluster) (*mongo.Client, error) {
   376  	if cluster.Leader == nil || cluster.Leader.Name == "" {
   377  		return nil, fmt.Errorf("cluster has no leader")
   378  	}
   379  
   380  	leaderMember := cluster.GetMemberWithName(cluster.Leader.Name)
   381  	host := cluster.GetMemberAddrWithPort(*leaderMember)
   382  	return NewReplSetClient(context.TODO(), []string{host})
   383  }
   384  
   385  func (mgr *Manager) GetReplSetClientWithHosts(ctx context.Context, hosts []string) (*mongo.Client, error) {
   386  	if len(hosts) == 0 {
   387  		err := errors.New("Get replset client whitout hosts")
   388  		mgr.Logger.Error(err, "Get replset client whitout hosts")
   389  		return nil, err
   390  	}
   391  
   392  	opts := options.Client().
   393  		SetHosts(hosts).
   394  		SetReplicaSet(config.replSetName).
   395  		SetAuth(options.Credential{
   396  			Password: config.password,
   397  			Username: config.username,
   398  		}).
   399  		SetWriteConcern(writeconcern.New(writeconcern.WMajority(), writeconcern.J(true))).
   400  		SetReadPreference(readpref.Primary()).
   401  		SetDirect(false)
   402  
   403  	client, err := mongo.Connect(ctx, opts)
   404  	if err != nil {
   405  		return nil, errors.Wrap(err, "connect to mongodb")
   406  	}
   407  	return client, err
   408  }
   409  
   410  func (mgr *Manager) IsCurrentMemberInCluster(ctx context.Context, cluster *dcs.Cluster) bool {
   411  	client, err := mgr.GetReplSetClient(ctx, cluster)
   412  	if err != nil {
   413  		mgr.Logger.Error(err, "Get replSet client failed")
   414  		return true
   415  	}
   416  	defer client.Disconnect(ctx) //nolint:errcheck
   417  
   418  	rsConfig, err := GetReplSetConfig(ctx, client)
   419  	if rsConfig == nil {
   420  		mgr.Logger.Error(err, "Get replSet config failed")
   421  		//
   422  		return true
   423  	}
   424  
   425  	for _, member := range rsConfig.Members {
   426  		if strings.HasPrefix(member.Host, mgr.GetCurrentMemberName()) {
   427  			return true
   428  		}
   429  	}
   430  
   431  	return false
   432  }
   433  
   434  func (mgr *Manager) IsCurrentMemberHealthy(ctx context.Context, cluster *dcs.Cluster) bool {
   435  	return mgr.IsMemberHealthy(ctx, cluster, nil)
   436  }
   437  
   438  func (mgr *Manager) IsMemberHealthy(ctx context.Context, cluster *dcs.Cluster, member *dcs.Member) bool {
   439  	var memberName string
   440  	if member != nil {
   441  		memberName = member.Name
   442  	} else {
   443  		memberName = mgr.CurrentMemberName
   444  	}
   445  
   446  	rsStatus, _ := mgr.GetReplSetStatus(ctx)
   447  	if rsStatus == nil {
   448  		return false
   449  	}
   450  
   451  	for _, member := range rsStatus.Members {
   452  		if strings.HasPrefix(member.Name, memberName) && member.Health == 1 {
   453  			return true
   454  		}
   455  	}
   456  	return false
   457  }
   458  
   459  func (mgr *Manager) Recover(context.Context) error {
   460  	return nil
   461  }
   462  
   463  func (mgr *Manager) JoinCurrentMemberToCluster(ctx context.Context, cluster *dcs.Cluster) error {
   464  	client, err := mgr.GetReplSetClient(ctx, cluster)
   465  	if err != nil {
   466  		return err
   467  	}
   468  	defer client.Disconnect(ctx) //nolint:errcheck
   469  
   470  	currentMember := cluster.GetMemberWithName(mgr.GetCurrentMemberName())
   471  	currentHost := cluster.GetMemberAddrWithPort(*currentMember)
   472  	rsConfig, err := GetReplSetConfig(ctx, client)
   473  	if rsConfig == nil {
   474  		mgr.Logger.Error(err, "Get replSet config failed")
   475  		return err
   476  	}
   477  
   478  	var lastID int
   479  	var configMember ConfigMember
   480  	for _, configMember = range rsConfig.Members {
   481  		if configMember.ID > lastID {
   482  			lastID = configMember.ID
   483  		}
   484  	}
   485  	configMember.ID = lastID + 1
   486  	configMember.Host = currentHost
   487  	configMember.Priority = SecondaryPriority
   488  	rsConfig.Members = append(rsConfig.Members, configMember)
   489  
   490  	rsConfig.Version++
   491  	return SetReplSetConfig(ctx, client, rsConfig)
   492  }
   493  
   494  func (mgr *Manager) LeaveMemberFromCluster(ctx context.Context, cluster *dcs.Cluster, memberName string) error {
   495  	client, err := mgr.GetReplSetClient(ctx, cluster)
   496  	if err != nil {
   497  		return err
   498  	}
   499  	defer client.Disconnect(ctx) //nolint:errcheck
   500  
   501  	rsConfig, err := GetReplSetConfig(ctx, client)
   502  	if rsConfig == nil {
   503  		mgr.Logger.Error(err, "Get replSet config failed")
   504  		return err
   505  	}
   506  
   507  	mgr.Logger.Info(fmt.Sprintf("Delete member: %s", memberName))
   508  	configMembers := make([]ConfigMember, 0, len(rsConfig.Members)-1)
   509  	for _, configMember := range rsConfig.Members {
   510  		if strings.HasPrefix(configMember.Host, memberName) {
   511  			configMembers = append(configMembers, configMember)
   512  		}
   513  	}
   514  
   515  	rsConfig.Members = configMembers
   516  	rsConfig.Version++
   517  	return SetReplSetConfig(ctx, client, rsConfig)
   518  }
   519  
   520  func (mgr *Manager) IsClusterHealthy(ctx context.Context, cluster *dcs.Cluster) bool {
   521  	client, err := mgr.GetReplSetClient(ctx, cluster)
   522  	if err != nil {
   523  		mgr.Logger.Error(err, "Get leader client failed")
   524  		return false
   525  	}
   526  	defer client.Disconnect(ctx) //nolint:errcheck
   527  
   528  	status, err := GetReplSetStatus(ctx, client)
   529  	if err != nil {
   530  		return false
   531  	}
   532  	mgr.Logger.Info(fmt.Sprintf("cluster status: %v", status))
   533  	return status.OK != 0
   534  }
   535  
   536  func (mgr *Manager) IsPromoted(ctx context.Context) bool {
   537  	isLeader, err := mgr.IsLeader(ctx, nil)
   538  	if err != nil || !isLeader {
   539  		mgr.Logger.Error(err, "Is leader check failed")
   540  		return false
   541  	}
   542  
   543  	rsConfig, err := mgr.GetReplSetConfig(ctx)
   544  	if rsConfig == nil {
   545  		mgr.Logger.Error(err, "Get replSet config failed")
   546  		return false
   547  	}
   548  	for i := range rsConfig.Members {
   549  		if strings.HasPrefix(rsConfig.Members[i].Host, mgr.CurrentMemberName) {
   550  			if rsConfig.Members[i].Priority == PrimaryPriority {
   551  				return true
   552  			}
   553  		}
   554  	}
   555  	return false
   556  }
   557  
   558  func (mgr *Manager) Promote(ctx context.Context, cluster *dcs.Cluster) error {
   559  	rsConfig, err := mgr.GetReplSetConfig(ctx)
   560  	if rsConfig == nil {
   561  		mgr.Logger.Error(err, "Get replSet config failed")
   562  		return err
   563  	}
   564  
   565  	for i := range rsConfig.Members {
   566  		if strings.HasPrefix(rsConfig.Members[i].Host, mgr.CurrentMemberName) {
   567  			if rsConfig.Members[i].Priority == PrimaryPriority {
   568  				mgr.Logger.Info("Current member already has the highest priority!")
   569  				return nil
   570  			}
   571  
   572  			rsConfig.Members[i].Priority = PrimaryPriority
   573  		} else if rsConfig.Members[i].Priority == PrimaryPriority {
   574  			rsConfig.Members[i].Priority = SecondaryPriority
   575  		}
   576  	}
   577  
   578  	rsConfig.Version++
   579  
   580  	hosts := mgr.GetMemberAddrsFromRSConfig(rsConfig)
   581  	client, err := NewReplSetClient(ctx, hosts)
   582  	if err != nil {
   583  		return err
   584  	}
   585  	defer client.Disconnect(ctx) //nolint:errcheck
   586  	mgr.Logger.Info("reconfig replset", "config", rsConfig)
   587  	return SetReplSetConfig(ctx, client, rsConfig)
   588  }
   589  
   590  func (mgr *Manager) Demote(context.Context) error {
   591  	// mongodb do premote and demote in one action, here do nothing.
   592  	return nil
   593  }
   594  
   595  func (mgr *Manager) Follow(ctx context.Context, cluster *dcs.Cluster) error {
   596  	return nil
   597  }
   598  
   599  func (mgr *Manager) GetHealthiestMember(cluster *dcs.Cluster, candidate string) *dcs.Member {
   600  	rsStatus, _ := mgr.GetReplSetStatus(context.TODO())
   601  	if rsStatus == nil {
   602  		return nil
   603  	}
   604  	healthyMembers := make([]string, 0, len(rsStatus.Members))
   605  	var leader string
   606  	for _, member := range rsStatus.Members {
   607  		if member.Health == 1 {
   608  			memberName := strings.Split(member.Name, ".")[0]
   609  			if memberName == candidate {
   610  				return cluster.GetMemberWithName(candidate)
   611  			}
   612  			healthyMembers = append(healthyMembers, memberName)
   613  			if member.State == 1 {
   614  				leader = memberName
   615  			}
   616  		}
   617  	}
   618  
   619  	if candidate != "" {
   620  		mgr.Logger.Info("no health member for candidate", "candidate", candidate)
   621  		return nil
   622  	}
   623  
   624  	if leader != "" {
   625  		return cluster.GetMemberWithName(leader)
   626  	}
   627  
   628  	// TODO: use lag and other info to pick the healthiest member
   629  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   630  	healthiestMember := healthyMembers[r.Intn(len(healthyMembers))]
   631  	return cluster.GetMemberWithName(healthiestMember)
   632  
   633  }
   634  
   635  func (mgr *Manager) HasOtherHealthyLeader(ctx context.Context, cluster *dcs.Cluster) *dcs.Member {
   636  	rsStatus, _ := mgr.GetReplSetStatus(ctx)
   637  	if rsStatus == nil {
   638  		return nil
   639  	}
   640  	healthMembers := map[string]struct{}{}
   641  	var otherLeader string
   642  	for _, member := range rsStatus.Members {
   643  		memberName := strings.Split(member.Name, ".")[0]
   644  		if member.State == 1 || member.State == 2 {
   645  			healthMembers[memberName] = struct{}{}
   646  		}
   647  
   648  		if member.State != 1 {
   649  			continue
   650  		}
   651  		if memberName != mgr.CurrentMemberName {
   652  			otherLeader = memberName
   653  		}
   654  	}
   655  	if otherLeader != "" {
   656  		return cluster.GetMemberWithName(otherLeader)
   657  	}
   658  
   659  	rsConfig, err := mgr.GetReplSetConfig(ctx)
   660  	if rsConfig == nil {
   661  		mgr.Logger.Error(err, "Get replSet config failed")
   662  		return nil
   663  	}
   664  
   665  	for _, mb := range rsConfig.Members {
   666  		memberName := strings.Split(mb.Host, ".")[0]
   667  		if mb.Priority == PrimaryPriority && memberName != mgr.CurrentMemberName {
   668  			if _, ok := healthMembers[memberName]; ok {
   669  				otherLeader = memberName
   670  			}
   671  		}
   672  	}
   673  
   674  	if otherLeader != "" {
   675  		return cluster.GetMemberWithName(otherLeader)
   676  	}
   677  
   678  	return nil
   679  }
   680  
   681  // HasOtherHealthyMembers Are there any healthy members other than the leader?
   682  func (mgr *Manager) HasOtherHealthyMembers(ctx context.Context, cluster *dcs.Cluster, leader string) []*dcs.Member {
   683  	members := make([]*dcs.Member, 0)
   684  	rsStatus, _ := mgr.GetReplSetStatus(ctx)
   685  	if rsStatus == nil {
   686  		return members
   687  	}
   688  
   689  	for _, member := range rsStatus.Members {
   690  		if member.Health != 1 {
   691  			continue
   692  		}
   693  		memberName := strings.Split(member.Name, ".")[0]
   694  		if memberName == leader {
   695  			continue
   696  		}
   697  		member := cluster.GetMemberWithName(memberName)
   698  		if member != nil {
   699  			members = append(members, member)
   700  		}
   701  	}
   702  
   703  	return members
   704  }
   705  
   706  func (mgr *Manager) Lock(ctx context.Context, reason string) error {
   707  	mgr.Logger.Info(fmt.Sprintf("Lock db: %s", reason))
   708  	m := bson.D{
   709  		{Key: "fsync", Value: 1},
   710  		{Key: "lock", Value: true},
   711  		{Key: "comment", Value: reason},
   712  	}
   713  	lockResp := LockResp{}
   714  
   715  	response := mgr.Client.Database("admin").RunCommand(ctx, m)
   716  	if response.Err() != nil {
   717  		mgr.Logger.Error(response.Err(), fmt.Sprintf("Lock db (%s) failed", reason))
   718  		return response.Err()
   719  	}
   720  	if err := response.Decode(&lockResp); err != nil {
   721  		err := errors.Wrap(err, "failed to decode lock response")
   722  		return err
   723  	}
   724  
   725  	if lockResp.OK != 1 {
   726  		err := errors.Errorf("mongo says: %s", lockResp.Errmsg)
   727  		return err
   728  	}
   729  	mgr.IsLocked = true
   730  	mgr.Logger.Info(fmt.Sprintf("Lock db success times: %d", lockResp.LockCount))
   731  	return nil
   732  }
   733  
   734  func (mgr *Manager) Unlock(ctx context.Context) error {
   735  	mgr.Logger.Info("Unlock db")
   736  	m := bson.M{"fsyncUnlock": 1}
   737  	unlockResp := LockResp{}
   738  	response := mgr.Client.Database("admin").RunCommand(ctx, m)
   739  	if response.Err() != nil {
   740  		mgr.Logger.Error(response.Err(), "Unlock db failed")
   741  		return response.Err()
   742  	}
   743  	if err := response.Decode(&unlockResp); err != nil {
   744  		err := errors.Wrap(err, "failed to decode unlock response")
   745  		return err
   746  	}
   747  
   748  	if unlockResp.OK != 1 {
   749  		err := errors.Errorf("mongo says: %s", unlockResp.Errmsg)
   750  		return err
   751  	}
   752  	for unlockResp.LockCount > 0 {
   753  		response = mgr.Client.Database("admin").RunCommand(ctx, m)
   754  		if response.Err() != nil {
   755  			mgr.Logger.Error(response.Err(), "Unlock db failed")
   756  			return response.Err()
   757  		}
   758  		if err := response.Decode(&unlockResp); err != nil {
   759  			err := errors.Wrap(err, "failed to decode unlock response")
   760  			return err
   761  		}
   762  	}
   763  	mgr.IsLocked = false
   764  	mgr.Logger.Info("Unlock db success")
   765  	return nil
   766  }