github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/replicaset/replicaset.go (about)

     1  package replicaset
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/juju/loggo"
    10  	"labix.org/v2/mgo"
    11  	"labix.org/v2/mgo/bson"
    12  )
    13  
    14  const (
    15  	// MaxPeers defines the maximum number of peers that mongo supports.
    16  	MaxPeers = 7
    17  
    18  	// maxInitiateAttempts is the maximum number of times to attempt
    19  	// replSetInitiate for each call to Initiate.
    20  	maxInitiateAttempts = 10
    21  
    22  	// initiateAttemptDelay is the amount of time to sleep between failed
    23  	// attempts to replSetInitiate.
    24  	initiateAttemptDelay = 100 * time.Millisecond
    25  
    26  	// rsMembersUnreachableError is the error message returned from mongo
    27  	// when it thinks that replicaset members are unreachable. This can
    28  	// occur if replSetInitiate is executed shortly after starting up mongo.
    29  	rsMembersUnreachableError = "all members and seeds must be reachable to initiate set"
    30  )
    31  
    32  var logger = loggo.GetLogger("juju.replicaset")
    33  
    34  // Initiate sets up a replica set with the given replica set name with the
    35  // single given member.  It need be called only once for a given mongo replica
    36  // set.  The tags specified will be added as tags on the member that is created
    37  // in the replica set.
    38  //
    39  // Note that you must set DialWithInfo and set Direct = true when dialing into a
    40  // specific non-initiated mongo server.
    41  //
    42  // See http://docs.mongodb.org/manual/reference/method/rs.initiate/ for more
    43  // details.
    44  func Initiate(session *mgo.Session, address, name string, tags map[string]string) error {
    45  	monotonicSession := session.Clone()
    46  	defer monotonicSession.Close()
    47  	monotonicSession.SetMode(mgo.Monotonic, true)
    48  	cfg := Config{
    49  		Name:    name,
    50  		Version: 1,
    51  		Members: []Member{{
    52  			Id:      1,
    53  			Address: address,
    54  			Tags:    tags,
    55  		}},
    56  	}
    57  	logger.Infof("Initiating replicaset with config %#v", cfg)
    58  	var err error
    59  	for i := 0; i < maxInitiateAttempts; i++ {
    60  		err = monotonicSession.Run(bson.D{{"replSetInitiate", cfg}}, nil)
    61  		if err != nil && err.Error() == rsMembersUnreachableError {
    62  			time.Sleep(initiateAttemptDelay)
    63  			continue
    64  		}
    65  		break
    66  	}
    67  
    68  	return err
    69  }
    70  
    71  // Member holds configuration information for a replica set member.
    72  //
    73  // See http://docs.mongodb.org/manual/reference/replica-configuration/
    74  // for more details
    75  type Member struct {
    76  	// Id is a unique id for a member in a set.
    77  	Id int `bson:"_id"`
    78  
    79  	// Address holds the network address of the member,
    80  	// in the form hostname:port.
    81  	Address string `bson:"host"`
    82  
    83  	// Arbiter holds whether the member is an arbiter only.
    84  	// This value is optional; it defaults to false.
    85  	Arbiter *bool `bson:"arbiterOnly,omitempty"`
    86  
    87  	// BuildIndexes determines whether the mongod builds indexes on this member.
    88  	// This value is optional; it defaults to true.
    89  	BuildIndexes *bool `bson:"buildIndexes,omitempty"`
    90  
    91  	// Hidden determines whether the replica set hides this member from
    92  	// the output of IsMaster.
    93  	// This value is optional; it defaults to false.
    94  	Hidden *bool `bson:"hidden,omitempty"`
    95  
    96  	// Priority determines eligibility of a member to become primary.
    97  	// This value is optional; it defaults to 1.
    98  	Priority *float64 `bson:"priority,omitempty"`
    99  
   100  	// Tags store additional information about a replica member, often used for
   101  	// customizing read preferences and write concern.
   102  	Tags map[string]string `bson:"tags,omitempty"`
   103  
   104  	// SlaveDelay describes the number of seconds behind the master that this
   105  	// replica set member should lag rounded up to the nearest second.
   106  	// This value is optional; it defaults to 0.
   107  	SlaveDelay *time.Duration `bson:"slaveDelay,omitempty"`
   108  
   109  	// Votes controls the number of votes a server has in a replica set election.
   110  	// This value is optional; it defaults to 1.
   111  	Votes *int `bson:"votes,omitempty"`
   112  }
   113  
   114  func fmtConfigForLog(config *Config) string {
   115  	memberInfo := make([]string, len(config.Members))
   116  	for i, member := range config.Members {
   117  		memberInfo[i] = fmt.Sprintf("Member{%d %q %v}", member.Id, member.Address, member.Tags)
   118  
   119  	}
   120  	return fmt.Sprintf("{Name: %s, Version: %d, Members: {%s}}",
   121  		config.Name, config.Version, strings.Join(memberInfo, ", "))
   122  }
   123  
   124  // applyRelSetConfig applies the new config to the mongo session. It also logs
   125  // what the changes are. It checks if the replica set changes cause the DB
   126  // connection to be dropped. If so, it Refreshes the session and tries to Ping
   127  // again.
   128  func applyRelSetConfig(cmd string, session *mgo.Session, oldconfig, newconfig *Config) error {
   129  	logger.Debugf("%s() changing replica set\nfrom %s\n  to %s",
   130  		cmd, fmtConfigForLog(oldconfig), fmtConfigForLog(newconfig))
   131  	err := session.Run(bson.D{{"replSetReconfig", newconfig}}, nil)
   132  	// We will only try to Ping 2 times
   133  	for i := 0; i < 2; i++ {
   134  		if err == io.EOF {
   135  			// If the primary changes due to replSetReconfig, then all
   136  			// current connections are dropped.
   137  			// Refreshing should fix us up.
   138  			logger.Debugf("got EOF while running %s(), calling session.Refresh()", cmd)
   139  			session.Refresh()
   140  		} else if err != nil {
   141  			// For all errors that aren't EOF, return immediately
   142  			return err
   143  		}
   144  		// err is either nil or EOF and we called Refresh, so Ping to
   145  		// make sure we're actually connected
   146  		err = session.Ping()
   147  		// Change the command because it is the new command we ran
   148  		cmd = "Ping"
   149  	}
   150  	return err
   151  }
   152  
   153  // Add adds the given members to the session's replica set.  Duplicates of
   154  // existing replicas will be ignored.
   155  //
   156  // Members will have their Ids set automatically if they are not already > 0
   157  func Add(session *mgo.Session, members ...Member) error {
   158  	config, err := CurrentConfig(session)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	oldconfig := *config
   164  	config.Version++
   165  	max := 0
   166  	for _, member := range config.Members {
   167  		if member.Id > max {
   168  			max = member.Id
   169  		}
   170  	}
   171  
   172  outerLoop:
   173  	for _, newMember := range members {
   174  		for _, member := range config.Members {
   175  			if member.Address == newMember.Address {
   176  				// already exists, skip it
   177  				continue outerLoop
   178  			}
   179  		}
   180  		// let the caller specify an id if they want, treat zero as unspecified
   181  		if newMember.Id < 1 {
   182  			max++
   183  			newMember.Id = max
   184  		}
   185  		config.Members = append(config.Members, newMember)
   186  	}
   187  	return applyRelSetConfig("Add", session, &oldconfig, config)
   188  }
   189  
   190  // Remove removes members with the given addresses from the replica set. It is
   191  // not an error to remove addresses of non-existent replica set members.
   192  func Remove(session *mgo.Session, addrs ...string) error {
   193  	config, err := CurrentConfig(session)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	oldconfig := *config
   198  	config.Version++
   199  	for _, rem := range addrs {
   200  		for n, repl := range config.Members {
   201  			if repl.Address == rem {
   202  				config.Members = append(config.Members[:n], config.Members[n+1:]...)
   203  				break
   204  			}
   205  		}
   206  	}
   207  	return applyRelSetConfig("Remove", session, &oldconfig, config)
   208  }
   209  
   210  // Set changes the current set of replica set members.  Members will have their
   211  // ids set automatically if their ids are not already > 0.
   212  func Set(session *mgo.Session, members []Member) error {
   213  	config, err := CurrentConfig(session)
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	// Copy the current configuration for logging
   219  	oldconfig := *config
   220  	config.Version++
   221  
   222  	// Assign ids to members that did not previously exist, starting above the
   223  	// value of the highest id that already existed
   224  	ids := map[string]int{}
   225  	max := 0
   226  	for _, m := range config.Members {
   227  		ids[m.Address] = m.Id
   228  		if m.Id > max {
   229  			max = m.Id
   230  		}
   231  	}
   232  
   233  	for x, m := range members {
   234  		if id, ok := ids[m.Address]; ok {
   235  			m.Id = id
   236  		} else if m.Id < 1 {
   237  			max++
   238  			m.Id = max
   239  		}
   240  		members[x] = m
   241  	}
   242  
   243  	config.Members = members
   244  
   245  	return applyRelSetConfig("Set", session, &oldconfig, config)
   246  }
   247  
   248  // Config reports information about the configuration of a given mongo node
   249  type IsMasterResults struct {
   250  	// The following fields hold information about the specific mongodb node.
   251  	IsMaster  bool      `bson:"ismaster"`
   252  	Secondary bool      `bson:"secondary"`
   253  	Arbiter   bool      `bson:"arbiterOnly"`
   254  	Address   string    `bson:"me"`
   255  	LocalTime time.Time `bson:"localTime"`
   256  
   257  	// The following fields hold information about the replica set.
   258  	ReplicaSetName string   `bson:"setName"`
   259  	Addresses      []string `bson:"hosts"`
   260  	Arbiters       []string `bson:"arbiters"`
   261  	PrimaryAddress string   `bson:"primary"`
   262  }
   263  
   264  // IsMaster returns information about the configuration of the node that
   265  // the given session is connected to.
   266  func IsMaster(session *mgo.Session) (*IsMasterResults, error) {
   267  	results := &IsMasterResults{}
   268  	err := session.Run("isMaster", results)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	return results, nil
   273  }
   274  
   275  var ErrMasterNotConfigured = fmt.Errorf("mongo master not configured")
   276  
   277  // MasterHostPort returns the "address:port" string for the primary
   278  // mongo server in the replicaset. It returns ErrMasterNotConfigured if
   279  // the replica set has not yet been initiated.
   280  func MasterHostPort(session *mgo.Session) (string, error) {
   281  	results, err := IsMaster(session)
   282  	if err != nil {
   283  		return "", err
   284  	}
   285  	if results.PrimaryAddress == "" {
   286  		return "", ErrMasterNotConfigured
   287  	}
   288  	return results.PrimaryAddress, nil
   289  }
   290  
   291  // CurrentMembers returns the current members of the replica set.
   292  func CurrentMembers(session *mgo.Session) ([]Member, error) {
   293  	cfg, err := CurrentConfig(session)
   294  	if err != nil {
   295  		return nil, err
   296  	}
   297  	return cfg.Members, nil
   298  }
   299  
   300  // CurrentConfig returns the Config for the given session's replica set.  If
   301  // there is no current config, the error returned will be mgo.ErrNotFound.
   302  func CurrentConfig(session *mgo.Session) (*Config, error) {
   303  	cfg := &Config{}
   304  	monotonicSession := session.Clone()
   305  	defer monotonicSession.Close()
   306  	monotonicSession.SetMode(mgo.Monotonic, true)
   307  	err := monotonicSession.DB("local").C("system.replset").Find(nil).One(cfg)
   308  	if err == mgo.ErrNotFound {
   309  		return nil, err
   310  	}
   311  	if err != nil {
   312  		return nil, fmt.Errorf("cannot get replset config: %s", err.Error())
   313  	}
   314  	return cfg, nil
   315  }
   316  
   317  // Config is the document stored in mongodb that defines the servers in the
   318  // replica set
   319  type Config struct {
   320  	Name    string   `bson:"_id"`
   321  	Version int      `bson:"version"`
   322  	Members []Member `bson:"members"`
   323  }
   324  
   325  // CurrentStatus returns the status of the replica set for the given session.
   326  func CurrentStatus(session *mgo.Session) (*Status, error) {
   327  	status := &Status{}
   328  	err := session.Run("replSetGetStatus", status)
   329  	if err != nil {
   330  		return nil, fmt.Errorf("cannot get replica set status: %v", err)
   331  	}
   332  	return status, nil
   333  }
   334  
   335  // Status holds data about the status of members of the replica set returned
   336  // from replSetGetStatus
   337  //
   338  // See http://docs.mongodb.org/manual/reference/command/replSetGetStatus/#dbcmd.replSetGetStatus
   339  type Status struct {
   340  	Name    string         `bson:"set"`
   341  	Members []MemberStatus `bson:"members"`
   342  }
   343  
   344  // Status holds the status of a replica set member returned from
   345  // replSetGetStatus.
   346  type MemberStatus struct {
   347  	// Id holds the replica set id of the member that the status is describing.
   348  	Id int `bson:"_id"`
   349  
   350  	// Address holds address of the member that the status is describing.
   351  	Address string `bson:"name"`
   352  
   353  	// Self holds whether this is the status for the member that
   354  	// the session is connected to.
   355  	Self bool `bson:"self"`
   356  
   357  	// ErrMsg holds the most recent error or status message received
   358  	// from the member.
   359  	ErrMsg string `bson:"errmsg"`
   360  
   361  	// Healthy reports whether the member is up. It is true for the
   362  	// member that the request was made to.
   363  	Healthy bool `bson:"health"`
   364  
   365  	// State describes the current state of the member.
   366  	State MemberState `bson:"state"`
   367  
   368  	// Uptime describes how long the member has been online.
   369  	Uptime time.Duration `bson:"uptime"`
   370  
   371  	// Ping describes the length of time a round-trip packet takes to travel
   372  	// between the remote member and the local instance.  It is zero for the
   373  	// member that the session is connected to.
   374  	Ping time.Duration `bson:"pingMS"`
   375  }
   376  
   377  // MemberState represents the state of a replica set member.
   378  // See http://docs.mongodb.org/manual/reference/replica-states/
   379  type MemberState int
   380  
   381  const (
   382  	StartupState = iota
   383  	PrimaryState
   384  	SecondaryState
   385  	RecoveringState
   386  	FatalState
   387  	Startup2State
   388  	UnknownState
   389  	ArbiterState
   390  	DownState
   391  	RollbackState
   392  	ShunnedState
   393  )
   394  
   395  var memberStateStrings = []string{
   396  	StartupState:    "STARTUP",
   397  	PrimaryState:    "PRIMARY",
   398  	SecondaryState:  "SECONDARY",
   399  	RecoveringState: "RECOVERING",
   400  	FatalState:      "FATAL",
   401  	Startup2State:   "STARTUP2",
   402  	UnknownState:    "UNKNOWN",
   403  	ArbiterState:    "ARBITER",
   404  	DownState:       "DOWN",
   405  	RollbackState:   "ROLLBACK",
   406  	ShunnedState:    "SHUNNED",
   407  }
   408  
   409  // String returns a string describing the state.
   410  func (state MemberState) String() string {
   411  	if state < 0 || int(state) >= len(memberStateStrings) {
   412  		return "INVALID_MEMBER_STATE"
   413  	}
   414  	return memberStateStrings[state]
   415  }