github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/peergrouper/mock_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  	"encoding/json"
     8  	"fmt"
     9  	"math/rand"
    10  	"path"
    11  	"reflect"
    12  	"strconv"
    13  	"sync"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/replicaset/v3"
    17  	"github.com/juju/utils/v3/voyeur"
    18  	"github.com/juju/worker/v3"
    19  	"gopkg.in/tomb.v2"
    20  
    21  	"github.com/juju/juju/controller"
    22  	"github.com/juju/juju/core/network"
    23  	"github.com/juju/juju/core/status"
    24  	"github.com/juju/juju/environs/config"
    25  	"github.com/juju/juju/state"
    26  	coretesting "github.com/juju/juju/testing"
    27  )
    28  
    29  // This file holds helper functions for mocking pieces of State and replicaset
    30  // that we don't want to directly depend on in unit tests.
    31  
    32  type fakeState struct {
    33  	mu               sync.Mutex
    34  	errors           errorPatterns
    35  	controllers      map[string]*fakeController
    36  	controllerInfo   voyeur.Value // of *state.ControllerInfo
    37  	statuses         voyeur.Value // of statuses collection
    38  	controllerConfig voyeur.Value // of controller.Config
    39  	session          *fakeMongoSession
    40  	checkMu          sync.Mutex
    41  	check            func(st *fakeState) error
    42  	spaces           map[string]*fakeSpace
    43  }
    44  
    45  var (
    46  	_ State          = (*fakeState)(nil)
    47  	_ ControllerNode = (*fakeController)(nil)
    48  	_ MongoSession   = (*fakeMongoSession)(nil)
    49  	_ Space          = (*fakeSpace)(nil)
    50  )
    51  
    52  type errorPatterns struct {
    53  	patterns []errorPattern
    54  }
    55  
    56  type errorPattern struct {
    57  	pattern string
    58  	errFunc func() error
    59  }
    60  
    61  // setErrorFor causes the given error to be returned
    62  // from any mock call that matches the given
    63  // string, which may contain wildcards as
    64  // in path.Match.
    65  //
    66  // The standard form for errors is:
    67  //
    68  //	Type.Function <arg>...
    69  //
    70  // See individual functions for details.
    71  func (e *errorPatterns) setErrorFor(what string, err error) {
    72  	e.setErrorFuncFor(what, func() error {
    73  		return err
    74  	})
    75  }
    76  
    77  // setErrorFuncFor causes the given function
    78  // to be invoked to return the error for the
    79  // given pattern.
    80  func (e *errorPatterns) setErrorFuncFor(what string, errFunc func() error) {
    81  	e.patterns = append(e.patterns, errorPattern{
    82  		pattern: what,
    83  		errFunc: errFunc,
    84  	})
    85  }
    86  
    87  // errorFor concatenates the call name
    88  // with all the args, space separated,
    89  // and returns any error registered with
    90  // setErrorFor that matches the resulting string.
    91  func (e *errorPatterns) errorFor(name string, args ...interface{}) error {
    92  	s := name
    93  	for _, arg := range args {
    94  		s += " " + fmt.Sprint(arg)
    95  	}
    96  	f := func() error { return nil }
    97  	for _, pattern := range e.patterns {
    98  		if ok, _ := path.Match(pattern.pattern, s); ok {
    99  			f = pattern.errFunc
   100  			break
   101  		}
   102  	}
   103  	err := f()
   104  	if err != nil {
   105  		logger.Errorf("errorFor %q -> %v", s, err)
   106  	}
   107  	return err
   108  }
   109  
   110  func (e *errorPatterns) resetErrors() {
   111  	e.patterns = e.patterns[:0]
   112  }
   113  
   114  func NewFakeState() *fakeState {
   115  	st := &fakeState{
   116  		controllers: make(map[string]*fakeController),
   117  	}
   118  	st.session = newFakeMongoSession(st, &st.errors)
   119  	st.controllerConfig.Set(controller.Config{})
   120  	return st
   121  }
   122  
   123  func (st *fakeState) getCheck() func(st *fakeState) error {
   124  	st.checkMu.Lock()
   125  	check := st.check
   126  	st.checkMu.Unlock()
   127  	return check
   128  }
   129  
   130  func (st *fakeState) setCheck(check func(st *fakeState) error) {
   131  	st.checkMu.Lock()
   132  	st.check = check
   133  	st.checkMu.Unlock()
   134  }
   135  
   136  func (st *fakeState) checkInvariants() {
   137  	check := st.getCheck()
   138  	if check == nil {
   139  		return
   140  	}
   141  	if err := check(st); err != nil {
   142  		// Force a panic, otherwise we can deadlock
   143  		// when called from within the worker.
   144  		go panic(err)
   145  		select {}
   146  	}
   147  }
   148  
   149  // checkInvariants checks that all the expected invariants
   150  // in the state hold true. Currently we check that:
   151  // - total number of votes is odd.
   152  // - member voting status implies that controller has vote.
   153  func checkInvariants(st *fakeState) error {
   154  	st.mu.Lock()
   155  	defer st.mu.Unlock()
   156  	members := st.session.members.Get().([]replicaset.Member)
   157  	voteCount := 0
   158  	for _, m := range members {
   159  		votes := 1
   160  		if m.Votes != nil {
   161  			votes = *m.Votes
   162  		}
   163  		voteCount += votes
   164  		if id, ok := m.Tags[jujuNodeKey]; ok {
   165  			if votes > 0 {
   166  				m := st.controllers[id]
   167  				if m == nil {
   168  					return fmt.Errorf("voting member with controller id %q has no associated Controller", id)
   169  				}
   170  			}
   171  		}
   172  	}
   173  	if voteCount%2 != 1 {
   174  		return fmt.Errorf("total vote count is not odd (got %d)", voteCount)
   175  	}
   176  	return nil
   177  }
   178  
   179  type invariantChecker interface {
   180  	checkInvariants()
   181  }
   182  
   183  // controller is similar to Controller except that
   184  // it bypasses the error mocking machinery.
   185  // It returns nil if there is no controller with the
   186  // given id.
   187  func (st *fakeState) controller(id string) *fakeController {
   188  	st.mu.Lock()
   189  	defer st.mu.Unlock()
   190  	return st.controllers[id]
   191  }
   192  
   193  func (st *fakeState) ControllerNode(id string) (ControllerNode, error) {
   194  	if err := st.errors.errorFor("State.ControllerNode", id); err != nil {
   195  		return nil, err
   196  	}
   197  	if m := st.controller(id); m != nil {
   198  		return m, nil
   199  	}
   200  	return nil, errors.NotFoundf("controller %s", id)
   201  }
   202  
   203  func (st *fakeState) ControllerHost(id string) (ControllerHost, error) {
   204  	if err := st.errors.errorFor("State.ControllerHost", id); err != nil {
   205  		return nil, err
   206  	}
   207  	if m := st.controller(id); m != nil {
   208  		return m, nil
   209  	}
   210  	return nil, errors.NotFoundf("controller %s", id)
   211  }
   212  
   213  func (st *fakeState) addController(id string, wantsVote bool) *fakeController {
   214  	st.mu.Lock()
   215  	defer st.mu.Unlock()
   216  	logger.Infof("fakeState.addController %q", id)
   217  	if st.controllers[id] != nil {
   218  		panic(fmt.Errorf("id %q already used", id))
   219  	}
   220  	doc := controllerDoc{
   221  		id:        id,
   222  		wantsVote: wantsVote,
   223  		life:      state.Alive,
   224  	}
   225  	m := &fakeController{
   226  		errors:  &st.errors,
   227  		checker: st,
   228  	}
   229  	st.controllers[id] = m
   230  	m.val.Set(doc)
   231  	return m
   232  }
   233  
   234  func (st *fakeState) removeController(id string) {
   235  	st.mu.Lock()
   236  	defer st.mu.Unlock()
   237  	if st.controllers[id] == nil {
   238  		panic(fmt.Errorf("removing non-existent controller %q", id))
   239  	}
   240  	delete(st.controllers, id)
   241  }
   242  
   243  func (st *fakeState) setControllers(ids ...string) {
   244  	st.controllerInfo.Set(&state.ControllerInfo{
   245  		ControllerIds: ids,
   246  	})
   247  }
   248  
   249  func (st *fakeState) ControllerIds() ([]string, error) {
   250  	if err := st.errors.errorFor("State.ControllerIds"); err != nil {
   251  		return nil, err
   252  	}
   253  	return deepCopy(st.controllerInfo.Get()).(*state.ControllerInfo).ControllerIds, nil
   254  }
   255  
   256  func (st *fakeState) WatchControllerInfo() state.StringsWatcher {
   257  	return WatchStrings(&st.controllerInfo)
   258  }
   259  
   260  func (st *fakeState) WatchControllerStatusChanges() state.StringsWatcher {
   261  	return WatchStrings(&st.statuses)
   262  }
   263  
   264  func (st *fakeState) WatchControllerConfig() state.NotifyWatcher {
   265  	return WatchValue(&st.controllerConfig)
   266  }
   267  
   268  func (st *fakeState) ModelConfig() (*config.Config, error) {
   269  	attrs := coretesting.FakeConfig()
   270  	cfg, err := config.New(config.NoDefaults, attrs)
   271  	return cfg, err
   272  }
   273  
   274  func (st *fakeState) ControllerConfig() (controller.Config, error) {
   275  	st.mu.Lock()
   276  	defer st.mu.Unlock()
   277  
   278  	if err := st.errors.errorFor("State.ControllerConfig"); err != nil {
   279  		return nil, err
   280  	}
   281  	return deepCopy(st.controllerConfig.Get()).(controller.Config), nil
   282  }
   283  
   284  func (st *fakeState) RemoveControllerReference(c ControllerNode) error {
   285  	st.mu.Lock()
   286  	defer st.mu.Unlock()
   287  	controllerInfo := st.controllerInfo.Get().(*state.ControllerInfo)
   288  	controllerIds := controllerInfo.ControllerIds
   289  	var newControllerIds []string
   290  	controllerId := c.Id()
   291  	for _, id := range controllerIds {
   292  		if id == controllerId {
   293  			continue
   294  		}
   295  		newControllerIds = append(newControllerIds, id)
   296  	}
   297  	st.setControllers(newControllerIds...)
   298  	return nil
   299  }
   300  
   301  func (st *fakeState) setHASpace(spaceName string) {
   302  	st.mu.Lock()
   303  	defer st.mu.Unlock()
   304  
   305  	// Ensure the configured space always exists in state.
   306  	if spaceName != network.AlphaSpaceName {
   307  		if st.spaces == nil {
   308  			st.spaces = make(map[string]*fakeSpace)
   309  		}
   310  		st.spaces[spaceName] = &fakeSpace{network.SpaceInfo{ID: spaceName, Name: network.SpaceName(spaceName)}}
   311  	}
   312  
   313  	cfg := st.controllerConfig.Get().(controller.Config)
   314  	cfg[controller.JujuHASpace] = spaceName
   315  	st.controllerConfig.Set(cfg)
   316  }
   317  
   318  func (st *fakeState) Space(name string) (Space, error) {
   319  	// Return a representation of the default space whenever requested.
   320  	if name == network.AlphaSpaceName {
   321  		return &fakeSpace{network.SpaceInfo{}}, nil
   322  	}
   323  
   324  	st.mu.Lock()
   325  	defer st.mu.Unlock()
   326  
   327  	space, ok := st.spaces[name]
   328  	if !ok {
   329  		return nil, errors.NotFoundf("space %q", name)
   330  	}
   331  	return space, nil
   332  }
   333  
   334  type fakeController struct {
   335  	mu      sync.Mutex
   336  	errors  *errorPatterns
   337  	val     voyeur.Value // of controllerDoc
   338  	checker invariantChecker
   339  }
   340  
   341  type controllerDoc struct {
   342  	id         string
   343  	wantsVote  bool
   344  	hasVote    bool
   345  	addresses  []network.SpaceAddress
   346  	statusInfo status.StatusInfo
   347  	life       state.Life
   348  }
   349  
   350  func (m *fakeController) doc() controllerDoc {
   351  	return m.val.Get().(controllerDoc)
   352  }
   353  
   354  func (m *fakeController) Refresh() error {
   355  	m.mu.Lock()
   356  	defer m.mu.Unlock()
   357  	doc := m.doc()
   358  	if err := m.errors.errorFor("Controller.Refresh", doc.id); err != nil {
   359  		return err
   360  	}
   361  	return nil
   362  }
   363  
   364  func (m *fakeController) GoString() string {
   365  	m.mu.Lock()
   366  	defer m.mu.Unlock()
   367  	return fmt.Sprintf("&fakeController{%#v}", m.doc())
   368  }
   369  
   370  func (m *fakeController) Id() string {
   371  	m.mu.Lock()
   372  	defer m.mu.Unlock()
   373  	return m.doc().id
   374  }
   375  
   376  func (m *fakeController) Life() state.Life {
   377  	m.mu.Lock()
   378  	defer m.mu.Unlock()
   379  	return m.doc().life
   380  }
   381  
   382  func (m *fakeController) Watch() state.NotifyWatcher {
   383  	m.mu.Lock()
   384  	defer m.mu.Unlock()
   385  	return WatchValue(&m.val)
   386  }
   387  
   388  func (m *fakeController) WantsVote() bool {
   389  	m.mu.Lock()
   390  	defer m.mu.Unlock()
   391  	return m.doc().wantsVote
   392  }
   393  
   394  func (m *fakeController) HasVote() bool {
   395  	m.mu.Lock()
   396  	defer m.mu.Unlock()
   397  	return m.doc().hasVote
   398  }
   399  
   400  func (m *fakeController) Addresses() network.SpaceAddresses {
   401  	m.mu.Lock()
   402  	defer m.mu.Unlock()
   403  	return m.doc().addresses
   404  }
   405  
   406  func (m *fakeController) Status() (status.StatusInfo, error) {
   407  	m.mu.Lock()
   408  	defer m.mu.Unlock()
   409  	return m.doc().statusInfo, nil
   410  }
   411  
   412  func (m *fakeController) SetStatus(sInfo status.StatusInfo) error {
   413  	m.mutate(func(doc *controllerDoc) {
   414  		doc.statusInfo = sInfo
   415  	})
   416  	return nil
   417  }
   418  
   419  // mutate atomically changes the controllerDoc of
   420  // the receiver by mutating it with the provided function.
   421  func (m *fakeController) mutate(f func(*controllerDoc)) {
   422  	m.mu.Lock()
   423  	doc := m.doc()
   424  	f(&doc)
   425  	m.val.Set(doc)
   426  	m.checker.checkInvariants()
   427  	m.mu.Unlock()
   428  }
   429  
   430  func (m *fakeController) setAddresses(addrs ...network.SpaceAddress) {
   431  	m.mutate(func(doc *controllerDoc) {
   432  		doc.addresses = addrs
   433  	})
   434  }
   435  
   436  // SetHasVote implements Controller.SetHasVote.
   437  func (m *fakeController) SetHasVote(hasVote bool) error {
   438  	doc := m.doc()
   439  	if err := m.errors.errorFor("Controller.SetHasVote", doc.id, hasVote); err != nil {
   440  		return err
   441  	}
   442  	m.mutate(func(doc *controllerDoc) {
   443  		doc.hasVote = hasVote
   444  	})
   445  	return nil
   446  }
   447  
   448  func (m *fakeController) setWantsVote(wantsVote bool) {
   449  	m.mutate(func(doc *controllerDoc) {
   450  		doc.wantsVote = wantsVote
   451  	})
   452  }
   453  
   454  func (m *fakeController) advanceLifecycle(life state.Life, wantsVote bool) {
   455  	m.mutate(func(doc *controllerDoc) {
   456  		doc.life = life
   457  		doc.wantsVote = wantsVote
   458  	})
   459  }
   460  
   461  type fakeSpace struct {
   462  	network.SpaceInfo
   463  }
   464  
   465  func (s *fakeSpace) NetworkSpace() (network.SpaceInfo, error) {
   466  	return s.SpaceInfo, nil
   467  }
   468  
   469  type fakeMongoSession struct {
   470  	// If InstantlyReady is true, replica status of
   471  	// all members will be instantly reported as ready.
   472  	InstantlyReady bool
   473  
   474  	errors  *errorPatterns
   475  	checker invariantChecker
   476  	members voyeur.Value // of []replicaset.Member
   477  	status  voyeur.Value // of *replicaset.Status
   478  }
   479  
   480  // newFakeMongoSession returns a mock implementation of mongoSession.
   481  func newFakeMongoSession(checker invariantChecker, errors *errorPatterns) *fakeMongoSession {
   482  	s := new(fakeMongoSession)
   483  	s.checker = checker
   484  	s.errors = errors
   485  	return s
   486  }
   487  
   488  // CurrentMembers implements mongoSession.CurrentMembers.
   489  func (session *fakeMongoSession) CurrentMembers() ([]replicaset.Member, error) {
   490  	if err := session.errors.errorFor("Session.CurrentMembers"); err != nil {
   491  		return nil, err
   492  	}
   493  	return deepCopy(session.members.Get()).([]replicaset.Member), nil
   494  }
   495  
   496  // CurrentStatus implements mongoSession.CurrentStatus.
   497  func (session *fakeMongoSession) CurrentStatus() (*replicaset.Status, error) {
   498  	if err := session.errors.errorFor("Session.CurrentStatus"); err != nil {
   499  		return nil, err
   500  	}
   501  	return deepCopy(session.status.Get()).(*replicaset.Status), nil
   502  }
   503  
   504  func (session *fakeMongoSession) currentPrimary() string {
   505  	members := session.members.Get().([]replicaset.Member)
   506  	status := session.status.Get().(*replicaset.Status)
   507  	for _, statusMember := range status.Members {
   508  		if statusMember.State == replicaset.PrimaryState {
   509  			for _, member := range members {
   510  				if member.Id == statusMember.Id {
   511  					return member.Tags["juju-machine-id"]
   512  				}
   513  			}
   514  		}
   515  	}
   516  	return ""
   517  }
   518  
   519  // setStatus sets the status of the current members of the session.
   520  func (session *fakeMongoSession) setStatus(members []replicaset.MemberStatus) {
   521  	session.status.Set(deepCopy(&replicaset.Status{
   522  		Members: members,
   523  	}))
   524  }
   525  
   526  // Set implements mongoSession.Set
   527  func (session *fakeMongoSession) Set(members []replicaset.Member) error {
   528  	if err := session.errors.errorFor("Session.Set"); err != nil {
   529  		logger.Infof("NOT setting replicaset members to \n%s", prettyReplicaSetMembersSlice(members))
   530  		return err
   531  	}
   532  	logger.Infof("setting replicaset members to \n%s", prettyReplicaSetMembersSlice(members))
   533  	session.members.Set(deepCopy(members))
   534  	if session.InstantlyReady {
   535  		statuses := make([]replicaset.MemberStatus, len(members))
   536  		for i, m := range members {
   537  			statuses[i] = replicaset.MemberStatus{
   538  				Id:      m.Id,
   539  				Address: m.Address,
   540  				Healthy: true,
   541  				State:   replicaset.SecondaryState,
   542  			}
   543  			if i == 0 {
   544  				statuses[i].State = replicaset.PrimaryState
   545  			}
   546  		}
   547  		session.setStatus(statuses)
   548  	}
   549  	session.checker.checkInvariants()
   550  	return nil
   551  }
   552  
   553  func (session *fakeMongoSession) StepDownPrimary() error {
   554  	if err := session.errors.errorFor("Session.StepDownPrimary"); err != nil {
   555  		logger.Debugf("StepDownPrimary error: %v", err)
   556  		return err
   557  	}
   558  	logger.Debugf("StepDownPrimary")
   559  	status := session.status.Get().(*replicaset.Status)
   560  	members := session.members.Get().([]replicaset.Member)
   561  	membersById := make(map[int]replicaset.Member, len(members))
   562  	for _, member := range members {
   563  		membersById[member.Id] = member
   564  	}
   565  	// We use a simple algorithm, find the primary, and all the secondaries that don't have 0 priority. And then pick a
   566  	// random secondary and swap their states
   567  	primaryIndex := -1
   568  	secondaryIndexes := []int{}
   569  	var info []string
   570  	for i, statusMember := range status.Members {
   571  		if statusMember.State == replicaset.PrimaryState {
   572  			primaryIndex = i
   573  			info = append(info, fmt.Sprintf("%d: current primary", statusMember.Id))
   574  		} else if statusMember.State == replicaset.SecondaryState {
   575  			confMember := membersById[statusMember.Id]
   576  			if confMember.Priority == nil || *confMember.Priority > 0 {
   577  				secondaryIndexes = append(secondaryIndexes, i)
   578  				info = append(info, fmt.Sprintf("%d: eligible secondary", statusMember.Id))
   579  			} else {
   580  				info = append(info, fmt.Sprintf("%d: ineligible secondary", statusMember.Id))
   581  			}
   582  		}
   583  	}
   584  	if primaryIndex == -1 {
   585  		return errors.Errorf("no primary to step down, broken config?")
   586  	}
   587  	if len(secondaryIndexes) < 1 {
   588  		return errors.Errorf("no secondaries to switch to")
   589  	}
   590  	secondaryIndex := secondaryIndexes[rand.Intn(len(secondaryIndexes))]
   591  	status.Members[primaryIndex].State = replicaset.SecondaryState
   592  	status.Members[secondaryIndex].State = replicaset.PrimaryState
   593  	logger.Debugf("StepDownPrimary nominated %d to be the new primary from: %v",
   594  		status.Members[secondaryIndex].Id, info)
   595  	session.setStatus(status.Members)
   596  	return nil
   597  }
   598  
   599  func (session *fakeMongoSession) Refresh() {
   600  	// If this was a testing.Stub we would track that Refresh was called.
   601  }
   602  
   603  // prettyReplicaSetMembersSlice wraps prettyReplicaSetMembers for testing
   604  // purposes only.
   605  func prettyReplicaSetMembersSlice(members []replicaset.Member) string {
   606  	vrm := make(map[string]*replicaset.Member, len(members))
   607  	for i := range members {
   608  		m := members[i]
   609  		vrm[strconv.Itoa(m.Id)] = &m
   610  	}
   611  	return prettyReplicaSetMembers(vrm)
   612  }
   613  
   614  // deepCopy makes a deep copy of any type by marshalling
   615  // it as JSON, then unmarshalling it.
   616  func deepCopy(x interface{}) interface{} {
   617  	v := reflect.ValueOf(x)
   618  	data, err := json.Marshal(x)
   619  	if err != nil {
   620  		panic(fmt.Errorf("cannot marshal %#v: %v", x, err))
   621  	}
   622  	newv := reflect.New(v.Type())
   623  	if err := json.Unmarshal(data, newv.Interface()); err != nil {
   624  		panic(fmt.Errorf("cannot unmarshal %q into %s", data, newv.Type()))
   625  	}
   626  	// sanity check
   627  	newx := newv.Elem().Interface()
   628  	if !reflect.DeepEqual(newx, x) {
   629  		panic(fmt.Errorf("value not deep-copied correctly"))
   630  	}
   631  	return newx
   632  }
   633  
   634  type notifier struct {
   635  	tomb    tomb.Tomb
   636  	w       *voyeur.Watcher
   637  	changes chan struct{}
   638  }
   639  
   640  func (n *notifier) loop() {
   641  	for n.w.Next() {
   642  		select {
   643  		case n.changes <- struct{}{}:
   644  		case <-n.tomb.Dying():
   645  		}
   646  	}
   647  }
   648  
   649  // WatchValue returns a NotifyWatcher that triggers
   650  // when the given value changes. Its Wait and Err methods
   651  // never return a non-nil error.
   652  func WatchValue(val *voyeur.Value) state.NotifyWatcher {
   653  	n := &notifier{
   654  		w:       val.Watch(),
   655  		changes: make(chan struct{}),
   656  	}
   657  	n.tomb.Go(func() error {
   658  		n.loop()
   659  		return nil
   660  	})
   661  	return n
   662  }
   663  
   664  // Changes returns a channel that sends a value when the value changes.
   665  // The value itself can be retrieved by calling the value's Get method.
   666  func (n *notifier) Changes() <-chan struct{} {
   667  	return n.changes
   668  }
   669  
   670  // Kill stops the notifier but does not wait for it to finish.
   671  func (n *notifier) Kill() {
   672  	n.tomb.Kill(nil)
   673  	n.w.Close()
   674  }
   675  
   676  func (n *notifier) Err() error {
   677  	return n.tomb.Err()
   678  }
   679  
   680  // Wait waits for the notifier to finish. It always returns nil.
   681  func (n *notifier) Wait() error {
   682  	return n.tomb.Wait()
   683  }
   684  
   685  func (n *notifier) Stop() error {
   686  	return worker.Stop(n)
   687  }
   688  
   689  type stringsNotifier struct {
   690  	tomb    tomb.Tomb
   691  	w       *voyeur.Watcher
   692  	changes chan []string
   693  }
   694  
   695  // WatchStrings returns a StringsWatcher that triggers
   696  // when the given value changes. Its Wait and Err methods
   697  // never return a non-nil error.
   698  func WatchStrings(val *voyeur.Value) state.StringsWatcher {
   699  	n := &stringsNotifier{
   700  		w:       val.Watch(),
   701  		changes: make(chan []string),
   702  	}
   703  	n.tomb.Go(func() error {
   704  		n.loop()
   705  		return nil
   706  	})
   707  	return n
   708  }
   709  
   710  func (n *stringsNotifier) loop() {
   711  	for n.w.Next() {
   712  		select {
   713  		case n.changes <- []string{}:
   714  		case <-n.tomb.Dying():
   715  		}
   716  	}
   717  }
   718  
   719  // Changes returns a channel that sends a value when the value changes.
   720  // The value itself can be retrieved by calling the value's Get method.
   721  func (n *stringsNotifier) Changes() <-chan []string {
   722  	return n.changes
   723  }
   724  
   725  // Kill stops the notifier but does not wait for it to finish.
   726  func (n *stringsNotifier) Kill() {
   727  	n.tomb.Kill(nil)
   728  	n.w.Close()
   729  }
   730  
   731  func (n *stringsNotifier) Err() error {
   732  	return n.tomb.Err()
   733  }
   734  
   735  // Wait waits for the notifier to finish. It always returns nil.
   736  func (n *stringsNotifier) Wait() error {
   737  	return n.tomb.Wait()
   738  }
   739  
   740  func (n *stringsNotifier) Stop() error {
   741  	return worker.Stop(n)
   742  }