launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/replicaset/replicaset_test.go (about)

     1  package replicaset
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	gc "launchpad.net/gocheck"
     9  
    10  	"labix.org/v2/mgo"
    11  
    12  	coretesting "launchpad.net/juju-core/testing"
    13  	"launchpad.net/juju-core/utils"
    14  )
    15  
    16  var (
    17  	name = "juju"
    18  	root *coretesting.MgoInstance
    19  )
    20  
    21  func TestPackage(t *testing.T) {
    22  	gc.TestingT(t)
    23  }
    24  
    25  func newServer() (*coretesting.MgoInstance, error) {
    26  	inst := &coretesting.MgoInstance{Params: []string{"--replSet", name}}
    27  
    28  	err := inst.Start(true)
    29  	if err != nil {
    30  		return nil, fmt.Errorf("Error starting mongo server: %s", err.Error())
    31  	}
    32  
    33  	// by dialing right now, we'll wait until it's running
    34  	strategy := utils.AttemptStrategy{Total: time.Second * 5, Delay: time.Millisecond * 100}
    35  	attempt := strategy.Start()
    36  	for attempt.Next() {
    37  		var session *mgo.Session
    38  		session, err = inst.DialDirect()
    39  		if err != nil {
    40  			err = fmt.Errorf("Error dialing mongo server %q: %s", inst.Addr(), err.Error())
    41  		} else {
    42  			session.SetMode(mgo.Monotonic, true)
    43  			err = session.Ping()
    44  			if err != nil {
    45  				err = fmt.Errorf("Error pinging mongo server %q: %s", inst.Addr(), err.Error())
    46  			}
    47  			session.Close()
    48  		}
    49  		if err == nil || !attempt.HasNext() {
    50  			break
    51  		}
    52  	}
    53  	return inst, err
    54  }
    55  
    56  type MongoSuite struct{}
    57  
    58  var _ = gc.Suite(&MongoSuite{})
    59  
    60  func (s *MongoSuite) SetUpSuite(c *gc.C) {
    61  	var err error
    62  	// do all this stuff here, since we don't want to have to redo it for each test
    63  	root, err = newServer()
    64  	if err != nil {
    65  		c.Fatalf("Got error from Start of root server: %s", err.Error())
    66  	}
    67  	// note, this is an actual test around Initiate, but again, I don't want to
    68  	// have to redo it, so I just do it once.
    69  	dialAndTestInitiate(c)
    70  }
    71  
    72  func (s *MongoSuite) TearDownTest(c *gc.C) {
    73  	// remove all secondaries from the replicaset on test teardown
    74  	session, err := root.DialDirect()
    75  	if err != nil {
    76  		c.Logf("Failed to dial root during test cleanup: %v", err)
    77  		return
    78  	}
    79  	defer session.Close()
    80  	mems, err := CurrentMembers(session)
    81  	if err != nil {
    82  		c.Logf("Failed to get list of memners during test cleanup: %v", err)
    83  		return
    84  	}
    85  
    86  	addrs := []string{}
    87  	for _, m := range mems {
    88  		if root.Addr() != m.Address {
    89  			addrs = append(addrs, m.Address)
    90  		}
    91  	}
    92  	if err = Remove(session, addrs...); err != nil {
    93  		c.Logf("Error removing secondaries: %v", err)
    94  	}
    95  }
    96  
    97  func dialAndTestInitiate(c *gc.C) {
    98  	session := root.MustDialDirect()
    99  	defer session.Close()
   100  
   101  	err := Initiate(session, root.Addr(), name)
   102  	c.Assert(err, gc.IsNil)
   103  
   104  	// Ids start at 1 for us, so we can differentiate between set and unset
   105  	expectedMembers := []Member{Member{Id: 1, Address: root.Addr()}}
   106  
   107  	// need to set mode to strong so that we wait for the write to succeed
   108  	// before reading and thus ensure that we're getting consistent reads.
   109  	session.SetMode(mgo.Strong, false)
   110  
   111  	mems, err := CurrentMembers(session)
   112  	c.Assert(err, gc.IsNil)
   113  	c.Assert(mems, gc.DeepEquals, expectedMembers)
   114  
   115  	// now add some data so we get a more real-life test
   116  	loadData(session, c)
   117  }
   118  
   119  func loadData(session *mgo.Session, c *gc.C) {
   120  	type foo struct {
   121  		Name    string
   122  		Address string
   123  		Count   int
   124  	}
   125  
   126  	for col := 0; col < 10; col++ {
   127  		foos := make([]foo, 10000)
   128  		for n := range foos {
   129  			foos[n] = foo{
   130  				Name:    fmt.Sprintf("name_%d_%d", col, n),
   131  				Address: fmt.Sprintf("address_%d_%d", col, n),
   132  				Count:   n * (col + 1),
   133  			}
   134  		}
   135  
   136  		err := session.DB("testing").C(fmt.Sprintf("data%d", col)).Insert(foos)
   137  		c.Assert(err, gc.IsNil)
   138  	}
   139  }
   140  
   141  func (s *MongoSuite) TearDownSuite(c *gc.C) {
   142  	root.Destroy()
   143  }
   144  
   145  func (s *MongoSuite) TestAddRemoveSet(c *gc.C) {
   146  	session := root.MustDial()
   147  	defer session.Close()
   148  
   149  	members := make([]Member, 0, 5)
   150  
   151  	// Add should be idempotent, so re-adding root here shouldn't result in
   152  	// two copies of root in the replica set
   153  	members = append(members, Member{Address: root.Addr()})
   154  
   155  	instances := make([]*coretesting.MgoInstance, 0, 5)
   156  	instances = append(instances, root)
   157  
   158  	for x := 0; x < 4; x++ {
   159  		inst, err := newServer()
   160  		c.Assert(err, gc.IsNil)
   161  		instances = append(instances, inst)
   162  		defer inst.Destroy()
   163  		defer Remove(session, inst.Addr())
   164  
   165  		key := fmt.Sprintf("key%d", x)
   166  		val := fmt.Sprintf("val%d", x)
   167  
   168  		tags := map[string]string{key: val}
   169  
   170  		members = append(members, Member{Address: inst.Addr(), Tags: tags})
   171  	}
   172  
   173  	var err error
   174  
   175  	strategy := utils.AttemptStrategy{Total: time.Second * 30, Delay: time.Millisecond * 100}
   176  	attempt := strategy.Start()
   177  	for attempt.Next() {
   178  		err = Add(session, members...)
   179  		if err == nil || !attempt.HasNext() {
   180  			break
   181  		}
   182  	}
   183  	c.Assert(err, gc.IsNil)
   184  
   185  	expectedMembers := make([]Member, len(members))
   186  	for x, m := range members {
   187  		// Ids should start at 1 (for the root) and go up
   188  		m.Id = x + 1
   189  		expectedMembers[x] = m
   190  	}
   191  
   192  	var cfg *Config
   193  	attempt = strategy.Start()
   194  	for attempt.Next() {
   195  		cfg, err = CurrentConfig(session)
   196  		if err == nil || !attempt.HasNext() {
   197  			break
   198  		}
   199  	}
   200  
   201  	c.Assert(err, gc.IsNil)
   202  	c.Assert(cfg.Name, gc.Equals, name)
   203  
   204  	// 2 since we already changed it once
   205  	c.Assert(cfg.Version, gc.Equals, 2)
   206  
   207  	mems := cfg.Members
   208  
   209  	c.Assert(mems, gc.DeepEquals, expectedMembers)
   210  
   211  	// Now remove the last two Members
   212  	attempt = strategy.Start()
   213  	for attempt.Next() {
   214  		err = Remove(session, members[3].Address, members[4].Address)
   215  		if err == nil || !attempt.HasNext() {
   216  			break
   217  		}
   218  	}
   219  	c.Assert(err, gc.IsNil)
   220  
   221  	expectedMembers = expectedMembers[0:3]
   222  
   223  	mems, err = CurrentMembers(session)
   224  	c.Assert(err, gc.IsNil)
   225  	c.Assert(mems, gc.DeepEquals, expectedMembers)
   226  
   227  	// now let's mix it up and set the new members to a mix of the previous
   228  	// plus the new arbiter
   229  	mems = []Member{members[3], mems[2], mems[0], members[4]}
   230  
   231  	attempt = strategy.Start()
   232  	for attempt.Next() {
   233  		err = Set(session, mems)
   234  		if err == nil || !attempt.HasNext() {
   235  			break
   236  		}
   237  	}
   238  
   239  	c.Assert(err, gc.IsNil)
   240  
   241  	attempt = strategy.Start()
   242  	for attempt.Next() {
   243  		// can dial whichever replica address here, mongo will figure it out
   244  		session = instances[0].MustDialDirect()
   245  		err = session.Ping()
   246  		if err == nil || !attempt.HasNext() {
   247  			break
   248  		}
   249  	}
   250  	c.Assert(err, gc.IsNil)
   251  
   252  	expectedMembers = []Member{members[3], expectedMembers[2], expectedMembers[0], members[4]}
   253  
   254  	// any new members will get an id of max(other_ids...)+1
   255  	expectedMembers[0].Id = 4
   256  	expectedMembers[3].Id = 5
   257  
   258  	attempt = strategy.Start()
   259  	for attempt.Next() {
   260  		mems, err = CurrentMembers(session)
   261  		if err == nil || !attempt.HasNext() {
   262  			break
   263  		}
   264  	}
   265  	c.Assert(err, gc.IsNil)
   266  	c.Assert(mems, gc.DeepEquals, expectedMembers)
   267  }
   268  
   269  func (s *MongoSuite) TestIsMaster(c *gc.C) {
   270  	session := root.MustDial()
   271  	defer session.Close()
   272  
   273  	expected := IsMasterResults{
   274  		// The following fields hold information about the specific mongodb node.
   275  		IsMaster:  true,
   276  		Secondary: false,
   277  		Arbiter:   false,
   278  		Address:   root.Addr(),
   279  		LocalTime: time.Time{},
   280  
   281  		// The following fields hold information about the replica set.
   282  		ReplicaSetName: name,
   283  		Addresses:      []string{root.Addr()},
   284  		Arbiters:       nil,
   285  		PrimaryAddress: root.Addr(),
   286  	}
   287  
   288  	res, err := IsMaster(session)
   289  	c.Assert(err, gc.IsNil)
   290  	c.Check(closeEnough(res.LocalTime, time.Now()), gc.Equals, true)
   291  	res.LocalTime = time.Time{}
   292  	c.Check(*res, gc.DeepEquals, expected)
   293  }
   294  
   295  func (s *MongoSuite) TestCurrentStatus(c *gc.C) {
   296  	session := root.MustDial()
   297  	defer session.Close()
   298  
   299  	inst1, err := newServer()
   300  	c.Assert(err, gc.IsNil)
   301  	defer inst1.Destroy()
   302  	defer Remove(session, inst1.Addr())
   303  
   304  	inst2, err := newServer()
   305  	c.Assert(err, gc.IsNil)
   306  	defer inst2.Destroy()
   307  	defer Remove(session, inst2.Addr())
   308  
   309  	strategy := utils.AttemptStrategy{Total: time.Second * 30, Delay: time.Millisecond * 100}
   310  	attempt := strategy.Start()
   311  	for attempt.Next() {
   312  		err = Add(session, Member{Address: inst1.Addr()}, Member{Address: inst2.Addr()})
   313  		if err == nil || !attempt.HasNext() {
   314  			break
   315  		}
   316  	}
   317  	c.Assert(err, gc.IsNil)
   318  
   319  	expected := &Status{
   320  		Name: name,
   321  		Members: []MemberStatus{{
   322  			Id:      1,
   323  			Address: root.Addr(),
   324  			Self:    true,
   325  			ErrMsg:  "",
   326  			Healthy: true,
   327  			State:   PrimaryState,
   328  		}, {
   329  			Id:      2,
   330  			Address: inst1.Addr(),
   331  			Self:    false,
   332  			ErrMsg:  "",
   333  			Healthy: true,
   334  			State:   SecondaryState,
   335  		}, {
   336  			Id:      3,
   337  			Address: inst2.Addr(),
   338  			Self:    false,
   339  			ErrMsg:  "",
   340  			Healthy: true,
   341  			State:   SecondaryState,
   342  		}},
   343  	}
   344  
   345  	strategy.Total = time.Second * 60
   346  	attempt = strategy.Start()
   347  	var res *Status
   348  	for attempt.Next() {
   349  		var err error
   350  		res, err = CurrentStatus(session)
   351  		if err != nil {
   352  			if !attempt.HasNext() {
   353  				c.Errorf("Couldn't get status before timeout, got err: %v", err)
   354  				return
   355  			} else {
   356  				// try again
   357  				continue
   358  			}
   359  		}
   360  
   361  		if res.Members[0].State == PrimaryState &&
   362  			res.Members[1].State == SecondaryState &&
   363  			res.Members[2].State == SecondaryState {
   364  			break
   365  		}
   366  		if !attempt.HasNext() {
   367  			c.Errorf("Servers did not get into final state before timeout.  Status: %#v", res)
   368  			return
   369  		}
   370  	}
   371  
   372  	for x, _ := range res.Members {
   373  		// non-empty uptime and ping
   374  		c.Check(res.Members[x].Uptime, gc.Not(gc.Equals), 0)
   375  
   376  		// ping is always going to be zero since we're on localhost
   377  		// so we can't really test it right now
   378  
   379  		// now overwrite Uptime so it won't throw off DeepEquals
   380  		res.Members[x].Uptime = 0
   381  	}
   382  	c.Check(res, gc.DeepEquals, expected)
   383  }
   384  
   385  func closeEnough(expected, obtained time.Time) bool {
   386  	t := obtained.Sub(expected)
   387  	return (-500*time.Millisecond) < t && t < (500*time.Millisecond)
   388  }