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 }