github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/peergrouper/worker_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package peergrouper
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"time"
    10  
    11  	"github.com/juju/replicaset"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils/voyeur"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	"github.com/juju/juju/instance"
    17  	"github.com/juju/juju/network"
    18  	"github.com/juju/juju/state"
    19  	coretesting "github.com/juju/juju/testing"
    20  	"github.com/juju/juju/worker/workertest"
    21  )
    22  
    23  type TestIPVersion struct {
    24  	version           string
    25  	formatHostPort    string
    26  	formatHost        string
    27  	machineFormatHost string
    28  	extraHostPort     string
    29  	extraHost         string
    30  	extraAddress      string
    31  	addressType       network.AddressType
    32  }
    33  
    34  var (
    35  	testIPv4 = TestIPVersion{
    36  		version:           "IPv4",
    37  		formatHostPort:    "0.1.2.%d:%d",
    38  		formatHost:        "0.1.2.%d",
    39  		machineFormatHost: "0.1.2.%d",
    40  		extraHostPort:     "0.1.99.99:9876",
    41  		extraHost:         "0.1.99.13",
    42  		extraAddress:      "0.1.99.13:1234",
    43  		addressType:       network.IPv4Address,
    44  	}
    45  	testIPv6 = TestIPVersion{
    46  		version:           "IPv6",
    47  		formatHostPort:    "[2001:DB8::%d]:%d",
    48  		formatHost:        "[2001:DB8::%d]",
    49  		machineFormatHost: "2001:DB8::%d",
    50  		extraHostPort:     "[2001:DB8::99:99]:9876",
    51  		extraHost:         "2001:DB8::99:13",
    52  		extraAddress:      "[2001:DB8::99:13]:1234",
    53  		addressType:       network.IPv6Address,
    54  	}
    55  )
    56  
    57  // DoTestForIPv4AndIPv6 runs the passed test for IPv4 and IPv6.
    58  func DoTestForIPv4AndIPv6(t func(ipVersion TestIPVersion)) {
    59  	t(testIPv4)
    60  	t(testIPv6)
    61  }
    62  
    63  type workerSuite struct {
    64  	coretesting.BaseSuite
    65  }
    66  
    67  var _ = gc.Suite(&workerSuite{})
    68  
    69  func (s *workerSuite) SetUpTest(c *gc.C) {
    70  	s.BaseSuite.SetUpTest(c)
    71  }
    72  
    73  // InitState initializes the fake state with a single
    74  // replicaset member and numMachines machines
    75  // primed to vote.
    76  func InitState(c *gc.C, st *fakeState, numMachines int, ipVersion TestIPVersion) {
    77  	var ids []string
    78  	for i := 10; i < 10+numMachines; i++ {
    79  		id := fmt.Sprint(i)
    80  		m := st.addMachine(id, true)
    81  		m.setInstanceId(instance.Id("id-" + id))
    82  		m.setStateHostPort(fmt.Sprintf(ipVersion.formatHostPort, i, mongoPort))
    83  		ids = append(ids, id)
    84  		c.Assert(m.MongoHostPorts(), gc.HasLen, 1)
    85  
    86  		m.setAPIHostPorts(network.NewHostPorts(
    87  			apiPort, fmt.Sprintf(ipVersion.formatHost, i),
    88  		))
    89  	}
    90  	st.machine("10").SetHasVote(true)
    91  	st.setControllers(ids...)
    92  	st.session.Set(mkMembers("0v", ipVersion))
    93  	st.session.setStatus(mkStatuses("0p", ipVersion))
    94  	st.check = checkInvariants
    95  }
    96  
    97  // ExpectedAPIHostPorts returns the expected addresses
    98  // of the machines as created by InitState.
    99  func ExpectedAPIHostPorts(n int, ipVersion TestIPVersion) [][]network.HostPort {
   100  	servers := make([][]network.HostPort, n)
   101  	for i := range servers {
   102  		servers[i] = network.NewHostPorts(
   103  			apiPort,
   104  			fmt.Sprintf(ipVersion.formatHost, i+10),
   105  		)
   106  	}
   107  	return servers
   108  }
   109  
   110  func (s *workerSuite) TestSetsAndUpdatesMembers(c *gc.C) {
   111  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   112  		s.PatchValue(&pollInterval, 5*time.Millisecond)
   113  
   114  		st := NewFakeState()
   115  		InitState(c, st, 3, ipVersion)
   116  
   117  		memberWatcher := st.session.members.Watch()
   118  		mustNext(c, memberWatcher)
   119  		assertMembers(c, memberWatcher.Value(), mkMembers("0v", ipVersion))
   120  
   121  		logger.Infof("starting worker")
   122  		w, err := newWorker(st, noPublisher{}, false)
   123  		c.Assert(err, jc.ErrorIsNil)
   124  		defer workertest.CleanKill(c, w)
   125  
   126  		// Wait for the worker to set the initial members.
   127  		mustNext(c, memberWatcher)
   128  		assertMembers(c, memberWatcher.Value(), mkMembers("0v 1 2", ipVersion))
   129  
   130  		// Update the status of the new members
   131  		// and check that they become voting.
   132  		c.Logf("updating new member status")
   133  		st.session.setStatus(mkStatuses("0p 1s 2s", ipVersion))
   134  		mustNext(c, memberWatcher)
   135  		assertMembers(c, memberWatcher.Value(), mkMembers("0v 1v 2v", ipVersion))
   136  
   137  		c.Logf("adding another machine")
   138  		// Add another machine.
   139  		m13 := st.addMachine("13", false)
   140  		m13.setStateHostPort(fmt.Sprintf(ipVersion.formatHostPort, 13, mongoPort))
   141  		st.setControllers("10", "11", "12", "13")
   142  
   143  		c.Logf("waiting for new member to be added")
   144  		mustNext(c, memberWatcher)
   145  		assertMembers(c, memberWatcher.Value(), mkMembers("0v 1v 2v 3", ipVersion))
   146  
   147  		// Remove vote from an existing member;
   148  		// and give it to the new machine.
   149  		// Also set the status of the new machine to
   150  		// healthy.
   151  		c.Logf("removing vote from machine 10 and adding it to machine 13")
   152  		st.machine("10").setWantsVote(false)
   153  		st.machine("13").setWantsVote(true)
   154  
   155  		st.session.setStatus(mkStatuses("0p 1s 2s 3s", ipVersion))
   156  
   157  		// Check that the new machine gets the vote and the
   158  		// old machine loses it.
   159  		c.Logf("waiting for vote switch")
   160  		mustNext(c, memberWatcher)
   161  		assertMembers(c, memberWatcher.Value(), mkMembers("0 1v 2v 3v", ipVersion))
   162  
   163  		c.Logf("removing old machine")
   164  		// Remove the old machine.
   165  		st.removeMachine("10")
   166  		st.setControllers("11", "12", "13")
   167  
   168  		// Check that it's removed from the members.
   169  		c.Logf("waiting for removal")
   170  		mustNext(c, memberWatcher)
   171  		assertMembers(c, memberWatcher.Value(), mkMembers("1v 2v 3v", ipVersion))
   172  	})
   173  }
   174  
   175  func (s *workerSuite) TestHasVoteMaintainedEvenWhenReplicaSetFails(c *gc.C) {
   176  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   177  		st := NewFakeState()
   178  
   179  		// Simulate a state where we have four controllers,
   180  		// one has gone down, and we're replacing it:
   181  		// 0 - hasvote true, wantsvote false, down
   182  		// 1 - hasvote true, wantsvote true
   183  		// 2 - hasvote true, wantsvote true
   184  		// 3 - hasvote false, wantsvote true
   185  		//
   186  		// When it starts, the worker should move the vote from
   187  		// 0 to 3. We'll arrange things so that it will succeed in
   188  		// setting the membership but fail setting the HasVote
   189  		// to false.
   190  		InitState(c, st, 4, ipVersion)
   191  		st.machine("10").SetHasVote(true)
   192  		st.machine("11").SetHasVote(true)
   193  		st.machine("12").SetHasVote(true)
   194  		st.machine("13").SetHasVote(false)
   195  
   196  		st.machine("10").setWantsVote(false)
   197  		st.machine("11").setWantsVote(true)
   198  		st.machine("12").setWantsVote(true)
   199  		st.machine("13").setWantsVote(true)
   200  
   201  		st.session.Set(mkMembers("0v 1v 2v 3", ipVersion))
   202  		st.session.setStatus(mkStatuses("0H 1p 2s 3s", ipVersion))
   203  
   204  		// Make the worker fail to set HasVote to false
   205  		// after changing the replica set membership.
   206  		st.errors.setErrorFor("Machine.SetHasVote * false", errors.New("frood"))
   207  
   208  		memberWatcher := st.session.members.Watch()
   209  		mustNext(c, memberWatcher)
   210  		assertMembers(c, memberWatcher.Value(), mkMembers("0v 1v 2v 3", ipVersion))
   211  
   212  		w, err := newWorker(st, noPublisher{}, false)
   213  		c.Assert(err, jc.ErrorIsNil)
   214  		done := make(chan error)
   215  		go func() {
   216  			done <- w.Wait()
   217  		}()
   218  
   219  		// Wait for the worker to set the initial members.
   220  		mustNext(c, memberWatcher)
   221  		assertMembers(c, memberWatcher.Value(), mkMembers("0 1v 2v 3v", ipVersion))
   222  
   223  		// The worker should encounter an error setting the
   224  		// has-vote status to false and exit.
   225  		select {
   226  		case err := <-done:
   227  			c.Assert(err, gc.ErrorMatches, `cannot set HasVote removed: cannot set voting status of "[0-9]+" to false: frood`)
   228  		case <-time.After(coretesting.LongWait):
   229  			c.Fatalf("timed out waiting for worker to exit")
   230  		}
   231  
   232  		// Start the worker again - although the membership should
   233  		// not change, the HasVote status should be updated correctly.
   234  		st.errors.resetErrors()
   235  		w, err = newWorker(st, noPublisher{}, false)
   236  		c.Assert(err, jc.ErrorIsNil)
   237  		defer workertest.CleanKill(c, w)
   238  
   239  		// Watch all the machines for changes, so we can check
   240  		// their has-vote status without polling.
   241  		changed := make(chan struct{}, 1)
   242  		for i := 10; i < 14; i++ {
   243  			watcher := st.machine(fmt.Sprint(i)).val.Watch()
   244  			defer watcher.Close()
   245  			go func() {
   246  				for watcher.Next() {
   247  					select {
   248  					case changed <- struct{}{}:
   249  					default:
   250  					}
   251  				}
   252  			}()
   253  		}
   254  		timeout := time.After(coretesting.LongWait)
   255  	loop:
   256  		for {
   257  			select {
   258  			case <-changed:
   259  				correct := true
   260  				for i := 10; i < 14; i++ {
   261  					hasVote := st.machine(fmt.Sprint(i)).HasVote()
   262  					expectHasVote := i != 10
   263  					if hasVote != expectHasVote {
   264  						correct = false
   265  					}
   266  				}
   267  				if correct {
   268  					break loop
   269  				}
   270  			case <-timeout:
   271  				c.Fatalf("timed out waiting for vote to be set")
   272  			}
   273  		}
   274  	})
   275  }
   276  
   277  func (s *workerSuite) TestAddressChange(c *gc.C) {
   278  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   279  		st := NewFakeState()
   280  		InitState(c, st, 3, ipVersion)
   281  
   282  		memberWatcher := st.session.members.Watch()
   283  		mustNext(c, memberWatcher)
   284  		assertMembers(c, memberWatcher.Value(), mkMembers("0v", ipVersion))
   285  
   286  		logger.Infof("starting worker")
   287  		w, err := newWorker(st, noPublisher{}, false)
   288  		c.Assert(err, jc.ErrorIsNil)
   289  		defer workertest.CleanKill(c, w)
   290  
   291  		// Wait for the worker to set the initial members.
   292  		mustNext(c, memberWatcher)
   293  		assertMembers(c, memberWatcher.Value(), mkMembers("0v 1 2", ipVersion))
   294  
   295  		// Change an address and wait for it to be changed in the
   296  		// members.
   297  		st.machine("11").setStateHostPort(ipVersion.extraHostPort)
   298  
   299  		mustNext(c, memberWatcher)
   300  		expectMembers := mkMembers("0v 1 2", ipVersion)
   301  		expectMembers[1].Address = ipVersion.extraHostPort
   302  		assertMembers(c, memberWatcher.Value(), expectMembers)
   303  	})
   304  }
   305  
   306  var fatalErrorsTests = []struct {
   307  	errPattern string
   308  	err        error
   309  	expectErr  string
   310  }{{
   311  	errPattern: "State.ControllerInfo",
   312  	expectErr:  "cannot get controller info: sample",
   313  }, {
   314  	errPattern: "Machine.SetHasVote 11 true",
   315  	expectErr:  `cannot set HasVote added: cannot set voting status of "11" to true: sample`,
   316  }, {
   317  	errPattern: "Session.CurrentStatus",
   318  	expectErr:  "cannot get peergrouper info: cannot get replica set status: sample",
   319  }, {
   320  	errPattern: "Session.CurrentMembers",
   321  	expectErr:  "cannot get peergrouper info: cannot get replica set members: sample",
   322  }, {
   323  	errPattern: "State.Machine *",
   324  	expectErr:  `cannot get machine "10": sample`,
   325  }, {
   326  	errPattern: "Machine.InstanceId *",
   327  	expectErr:  `cannot get API server info: sample`,
   328  }}
   329  
   330  func (s *workerSuite) TestFatalErrors(c *gc.C) {
   331  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   332  		s.PatchValue(&pollInterval, 5*time.Millisecond)
   333  		for i, testCase := range fatalErrorsTests {
   334  			c.Logf("test %d: %s -> %s", i, testCase.errPattern, testCase.expectErr)
   335  			st := NewFakeState()
   336  			st.session.InstantlyReady = true
   337  			InitState(c, st, 3, ipVersion)
   338  			st.errors.setErrorFor(testCase.errPattern, errors.New("sample"))
   339  			w, err := newWorker(st, noPublisher{}, false)
   340  			c.Assert(err, jc.ErrorIsNil)
   341  			done := make(chan error)
   342  			go func() {
   343  				done <- w.Wait()
   344  			}()
   345  			select {
   346  			case err := <-done:
   347  				c.Assert(err, gc.ErrorMatches, testCase.expectErr)
   348  			case <-time.After(coretesting.LongWait):
   349  				c.Fatalf("timed out waiting for error")
   350  			}
   351  		}
   352  	})
   353  }
   354  
   355  func (s *workerSuite) TestSetMembersErrorIsNotFatal(c *gc.C) {
   356  	coretesting.SkipIfI386(c, "lp:1425569")
   357  
   358  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   359  		st := NewFakeState()
   360  		InitState(c, st, 3, ipVersion)
   361  		st.session.setStatus(mkStatuses("0p 1s 2s", ipVersion))
   362  		var setCount voyeur.Value
   363  		st.errors.setErrorFuncFor("Session.Set", func() error {
   364  			setCount.Set(true)
   365  			return errors.New("sample")
   366  		})
   367  		s.PatchValue(&initialRetryInterval, 10*time.Microsecond)
   368  		s.PatchValue(&maxRetryInterval, coretesting.ShortWait/4)
   369  
   370  		w, err := newWorker(st, noPublisher{}, false)
   371  		c.Assert(err, jc.ErrorIsNil)
   372  		defer workertest.CleanKill(c, w)
   373  
   374  		// See that the worker is retrying.
   375  		setCountW := setCount.Watch()
   376  		mustNext(c, setCountW)
   377  		mustNext(c, setCountW)
   378  		mustNext(c, setCountW)
   379  	})
   380  }
   381  
   382  type PublisherFunc func(apiServers [][]network.HostPort, instanceIds []instance.Id) error
   383  
   384  func (f PublisherFunc) publishAPIServers(apiServers [][]network.HostPort, instanceIds []instance.Id) error {
   385  	return f(apiServers, instanceIds)
   386  }
   387  
   388  func (s *workerSuite) TestControllersArePublished(c *gc.C) {
   389  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   390  		publishCh := make(chan [][]network.HostPort)
   391  		publish := func(apiServers [][]network.HostPort, instanceIds []instance.Id) error {
   392  			publishCh <- apiServers
   393  			return nil
   394  		}
   395  
   396  		st := NewFakeState()
   397  		InitState(c, st, 3, ipVersion)
   398  		w, err := newWorker(st, PublisherFunc(publish), false)
   399  		c.Assert(err, jc.ErrorIsNil)
   400  		defer workertest.CleanKill(c, w)
   401  
   402  		select {
   403  		case servers := <-publishCh:
   404  			AssertAPIHostPorts(c, servers, ExpectedAPIHostPorts(3, ipVersion))
   405  		case <-time.After(coretesting.LongWait):
   406  			c.Fatalf("timed out waiting for publish")
   407  		}
   408  
   409  		// Change one of the servers' API addresses and check that it's published.
   410  		var newMachine10APIHostPorts []network.HostPort
   411  		newMachine10APIHostPorts = network.NewHostPorts(apiPort, ipVersion.extraHost)
   412  		st.machine("10").setAPIHostPorts(newMachine10APIHostPorts)
   413  		select {
   414  		case servers := <-publishCh:
   415  			expected := ExpectedAPIHostPorts(3, ipVersion)
   416  			expected[0] = newMachine10APIHostPorts
   417  			AssertAPIHostPorts(c, servers, expected)
   418  		case <-time.After(coretesting.LongWait):
   419  			c.Fatalf("timed out waiting for publish")
   420  		}
   421  	})
   422  }
   423  
   424  func hostPortInSpace(address, spaceName string) network.HostPort {
   425  	netAddress := network.Address{
   426  		Value:     address,
   427  		Type:      network.IPv4Address,
   428  		Scope:     network.ScopeUnknown,
   429  		SpaceName: network.SpaceName(spaceName),
   430  	}
   431  	return network.HostPort{
   432  		Address: netAddress,
   433  		Port:    4711,
   434  	}
   435  }
   436  
   437  func mongoSpaceTestCommonSetup(c *gc.C, ipVersion TestIPVersion, noSpaces bool) (*fakeState, []string, []network.HostPort) {
   438  	st := NewFakeState()
   439  	InitState(c, st, 3, ipVersion)
   440  	var hostPorts []network.HostPort
   441  
   442  	if noSpaces {
   443  		hostPorts = []network.HostPort{
   444  			hostPortInSpace(fmt.Sprintf(ipVersion.machineFormatHost, 1), ""),
   445  			hostPortInSpace(fmt.Sprintf(ipVersion.machineFormatHost, 2), ""),
   446  			hostPortInSpace(fmt.Sprintf(ipVersion.machineFormatHost, 3), ""),
   447  		}
   448  	} else {
   449  		hostPorts = []network.HostPort{
   450  			hostPortInSpace(fmt.Sprintf(ipVersion.machineFormatHost, 1), "one"),
   451  			hostPortInSpace(fmt.Sprintf(ipVersion.machineFormatHost, 2), "two"),
   452  			hostPortInSpace(fmt.Sprintf(ipVersion.machineFormatHost, 3), "three"),
   453  		}
   454  	}
   455  
   456  	machines := []string{"10", "11", "12"}
   457  	for _, machine := range machines {
   458  		st.machine(machine).SetHasVote(true)
   459  		st.machine(machine).setWantsVote(true)
   460  	}
   461  
   462  	st.session.Set(mkMembers("0v 1v 2v", ipVersion))
   463  
   464  	return st, machines, hostPorts
   465  }
   466  
   467  func startWorkerSupportingSpaces(c *gc.C, st *fakeState, ipVersion TestIPVersion) *pgWorker {
   468  	w, err := newWorker(st, noPublisher{}, true)
   469  	c.Assert(err, jc.ErrorIsNil)
   470  	return w.(*pgWorker)
   471  }
   472  
   473  func runWorkerUntilMongoStateIs(c *gc.C, st *fakeState, w *pgWorker, mss state.MongoSpaceStates) {
   474  	changes := st.controllers.Watch()
   475  	changes.Next()
   476  	for st.getMongoSpaceState() != mss {
   477  		changes.Next()
   478  	}
   479  	workertest.CleanKill(c, w)
   480  }
   481  
   482  func (s *workerSuite) TestMongoFindAndUseSpace(c *gc.C) {
   483  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   484  		st, machines, hostPorts := mongoSpaceTestCommonSetup(c, ipVersion, false)
   485  
   486  		for i, machine := range machines {
   487  			// machine 10 gets a host port in space one
   488  			// machine 11 gets host ports in spaces one and two
   489  			// machine 12 gets host ports in spaces one, two and three
   490  			st.machine(machine).setMongoHostPorts(hostPorts[0 : i+1])
   491  		}
   492  
   493  		w := startWorkerSupportingSpaces(c, st, ipVersion)
   494  		runWorkerUntilMongoStateIs(c, st, w, state.MongoSpaceValid)
   495  
   496  		// Only space one has all three servers in it
   497  		c.Assert(st.getMongoSpaceName(), gc.Equals, "one")
   498  
   499  		// All machines have the same address in this test for simplicity. The
   500  		// space three address is 0.0.0.3 giving us the host port of 0.0.0.3:4711
   501  		members := st.session.members.Get().([]replicaset.Member)
   502  		c.Assert(members, gc.HasLen, 3)
   503  		for i := 0; i < 3; i++ {
   504  			c.Assert(members[i].Address, gc.Equals, fmt.Sprintf(ipVersion.formatHostPort, 1, 4711))
   505  		}
   506  	})
   507  }
   508  
   509  func (s *workerSuite) TestMongoErrorNoCommonSpace(c *gc.C) {
   510  	c.Skip("dimitern: test disabled as it needs refactoring")
   511  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   512  		st, machines, hostPorts := mongoSpaceTestCommonSetup(c, ipVersion, false)
   513  
   514  		for i, machine := range machines {
   515  			// machine 10 gets a host port in space one
   516  			// machine 11 gets a host port in space two
   517  			// machine 12 gets a host port in space three
   518  			st.machine(machine).setMongoHostPorts(hostPorts[i : i+1])
   519  		}
   520  
   521  		w := startWorkerSupportingSpaces(c, st, ipVersion)
   522  		done := make(chan error)
   523  		go func() {
   524  			done <- w.Wait()
   525  		}()
   526  		select {
   527  		case err := <-done:
   528  			c.Assert(err, gc.ErrorMatches, ".*couldn't find a space containing all peer group machines")
   529  		case <-time.After(coretesting.LongWait):
   530  			c.Fatalf("timed out waiting for worker to exit")
   531  		}
   532  
   533  		// Each machine is in a unique space, so the Mongo space should be empty
   534  		c.Assert(st.getMongoSpaceName(), gc.Equals, "")
   535  		c.Assert(st.getMongoSpaceState(), gc.Equals, state.MongoSpaceInvalid)
   536  	})
   537  }
   538  
   539  func (s *workerSuite) TestMongoNoSpaces(c *gc.C) {
   540  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   541  		st, machines, hostPorts := mongoSpaceTestCommonSetup(c, ipVersion, true)
   542  
   543  		for i, machine := range machines {
   544  			st.machine(machine).setMongoHostPorts(hostPorts[i : i+1])
   545  		}
   546  
   547  		w := startWorkerSupportingSpaces(c, st, ipVersion)
   548  		runWorkerUntilMongoStateIs(c, st, w, state.MongoSpaceValid)
   549  
   550  		// Only space one has all three servers in it
   551  		c.Assert(st.getMongoSpaceName(), gc.Equals, "")
   552  	})
   553  }
   554  
   555  func (s *workerSuite) TestMongoSpaceNotOverwritten(c *gc.C) {
   556  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   557  		st, machines, hostPorts := mongoSpaceTestCommonSetup(c, ipVersion, false)
   558  
   559  		for i, machine := range machines {
   560  			// machine 10 gets a host port in space one
   561  			// machine 11 gets host ports in spaces one and two
   562  			// machine 12 gets host ports in spaces one, two and three
   563  			st.machine(machine).setMongoHostPorts(hostPorts[0 : i+1])
   564  		}
   565  
   566  		w := startWorkerSupportingSpaces(c, st, ipVersion)
   567  		runWorkerUntilMongoStateIs(c, st, w, state.MongoSpaceValid)
   568  
   569  		// Only space one has all three servers in it
   570  		c.Assert(st.getMongoSpaceName(), gc.Equals, "one")
   571  
   572  		// Set st.mongoSpaceName to something different
   573  
   574  		st.SetMongoSpaceState(state.MongoSpaceUnknown)
   575  		st.SetOrGetMongoSpaceName("testing")
   576  
   577  		// Only space one has all three servers in it
   578  		c.Assert(st.getMongoSpaceName(), gc.Equals, "testing")
   579  		c.Assert(st.getMongoSpaceState(), gc.Equals, state.MongoSpaceValid)
   580  	})
   581  }
   582  
   583  func (s *workerSuite) TestMongoSpaceNotCalculatedWhenSpacesNotSupported(c *gc.C) {
   584  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   585  		st, machines, hostPorts := mongoSpaceTestCommonSetup(c, ipVersion, false)
   586  
   587  		for i, machine := range machines {
   588  			// machine 10 gets a host port in space one
   589  			// machine 11 gets host ports in spaces one and two
   590  			// machine 12 gets host ports in spaces one, two and three
   591  			st.machine(machine).setMongoHostPorts(hostPorts[0 : i+1])
   592  		}
   593  
   594  		// Set some garbage up to check that it isn't overwritten
   595  		st.SetOrGetMongoSpaceName("garbage")
   596  		st.SetMongoSpaceState(state.MongoSpaceUnknown)
   597  
   598  		// Start a worker that doesn't support spaces
   599  		w, err := newWorker(st, noPublisher{}, false)
   600  		c.Assert(err, jc.ErrorIsNil)
   601  		runWorkerUntilMongoStateIs(c, st, w.(*pgWorker), state.MongoSpaceUnsupported)
   602  
   603  		// Only space one has all three servers in it
   604  		c.Assert(st.getMongoSpaceName(), gc.Equals, "garbage")
   605  		c.Assert(st.getMongoSpaceState(), gc.Equals, state.MongoSpaceUnsupported)
   606  	})
   607  }
   608  
   609  func (s *workerSuite) TestWorkerRetriesOnPublishError(c *gc.C) {
   610  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   611  		s.PatchValue(&pollInterval, coretesting.LongWait+time.Second)
   612  		s.PatchValue(&initialRetryInterval, 5*time.Millisecond)
   613  		s.PatchValue(&maxRetryInterval, initialRetryInterval)
   614  
   615  		publishCh := make(chan [][]network.HostPort, 100)
   616  
   617  		count := 0
   618  		publish := func(apiServers [][]network.HostPort, instanceIds []instance.Id) error {
   619  			publishCh <- apiServers
   620  			count++
   621  			if count <= 3 {
   622  				return fmt.Errorf("publish error")
   623  			}
   624  			return nil
   625  		}
   626  		st := NewFakeState()
   627  		InitState(c, st, 3, ipVersion)
   628  
   629  		w, err := newWorker(st, PublisherFunc(publish), false)
   630  		c.Assert(err, jc.ErrorIsNil)
   631  		defer workertest.CleanKill(c, w)
   632  
   633  		for i := 0; i < 4; i++ {
   634  			select {
   635  			case servers := <-publishCh:
   636  				AssertAPIHostPorts(c, servers, ExpectedAPIHostPorts(3, ipVersion))
   637  			case <-time.After(coretesting.LongWait):
   638  				c.Fatalf("timed out waiting for publish #%d", i)
   639  			}
   640  		}
   641  		select {
   642  		case <-publishCh:
   643  			c.Errorf("unexpected publish event")
   644  		case <-time.After(coretesting.ShortWait):
   645  		}
   646  	})
   647  }
   648  
   649  func (s *workerSuite) TestWorkerPublishesInstanceIds(c *gc.C) {
   650  	DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) {
   651  		s.PatchValue(&pollInterval, coretesting.LongWait+time.Second)
   652  		s.PatchValue(&initialRetryInterval, 5*time.Millisecond)
   653  		s.PatchValue(&maxRetryInterval, initialRetryInterval)
   654  
   655  		publishCh := make(chan []instance.Id, 100)
   656  
   657  		publish := func(apiServers [][]network.HostPort, instanceIds []instance.Id) error {
   658  			publishCh <- instanceIds
   659  			return nil
   660  		}
   661  		st := NewFakeState()
   662  		InitState(c, st, 3, ipVersion)
   663  
   664  		w, err := newWorker(st, PublisherFunc(publish), false)
   665  		c.Assert(err, jc.ErrorIsNil)
   666  		defer workertest.CleanKill(c, w)
   667  
   668  		select {
   669  		case instanceIds := <-publishCh:
   670  			c.Assert(instanceIds, jc.SameContents, []instance.Id{"id-10", "id-11", "id-12"})
   671  		case <-time.After(coretesting.LongWait):
   672  			c.Errorf("timed out waiting for publish")
   673  		}
   674  	})
   675  }
   676  
   677  // mustNext waits for w's value to be set and returns it.
   678  func mustNext(c *gc.C, w *voyeur.Watcher) (val interface{}) {
   679  	type voyeurResult struct {
   680  		ok  bool
   681  		val interface{}
   682  	}
   683  	done := make(chan voyeurResult)
   684  	go func() {
   685  		c.Logf("mustNext %p", w)
   686  		ok := w.Next()
   687  		val = w.Value()
   688  		c.Logf("mustNext done %p, ok: %v, val: %#v", w, ok, val)
   689  		done <- voyeurResult{ok, val}
   690  	}()
   691  	select {
   692  	case result := <-done:
   693  		c.Assert(result.ok, jc.IsTrue)
   694  		return result.val
   695  	case <-time.After(coretesting.LongWait):
   696  		c.Fatalf("timed out waiting for value to be set")
   697  	}
   698  	panic("unreachable")
   699  }
   700  
   701  type noPublisher struct{}
   702  
   703  func (noPublisher) publishAPIServers(apiServers [][]network.HostPort, instanceIds []instance.Id) error {
   704  	return nil
   705  }