github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/apiconfigwatcher/manifold.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiconfigwatcher 5 6 import ( 7 "sort" 8 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "github.com/juju/utils/voyeur" 12 "gopkg.in/juju/worker.v1" 13 "gopkg.in/juju/worker.v1/dependency" 14 "gopkg.in/tomb.v2" 15 16 "github.com/juju/juju/agent" 17 ) 18 19 var logger = loggo.GetLogger("juju.worker.apiconfigwatcher") 20 21 type ManifoldConfig struct { 22 AgentName string 23 AgentConfigChanged *voyeur.Value 24 } 25 26 // Manifold returns a dependency.Manifold which wraps an agent's 27 // voyeur.Value which is set whenever the agent config is 28 // changed. When the API server addresses in the config change the 29 // manifold will bounce itself. 30 // 31 // The manifold is intended to be a dependency for the api-caller 32 // manifold and is required to support model migrations. 33 func Manifold(config ManifoldConfig) dependency.Manifold { 34 return dependency.Manifold{ 35 Inputs: []string{config.AgentName}, 36 Start: func(context dependency.Context) (worker.Worker, error) { 37 if config.AgentConfigChanged == nil { 38 return nil, errors.NotValidf("nil AgentConfigChanged") 39 } 40 41 var a agent.Agent 42 if err := context.Get(config.AgentName, &a); err != nil { 43 return nil, err 44 } 45 46 w := &apiconfigwatcher{ 47 agent: a, 48 agentConfigChanged: config.AgentConfigChanged, 49 addrs: getAPIAddresses(a), 50 } 51 w.tomb.Go(w.loop) 52 return w, nil 53 }, 54 } 55 } 56 57 type apiconfigwatcher struct { 58 tomb tomb.Tomb 59 agent agent.Agent 60 agentConfigChanged *voyeur.Value 61 addrs []string 62 } 63 64 func (w *apiconfigwatcher) loop() error { 65 watch := w.agentConfigChanged.Watch() 66 defer watch.Close() 67 68 // TODO(mjs) - this is pretty awful. There should be a 69 // NotifyWatcher for voyeur.Value. Note also that this code is 70 // repeated elsewhere. 71 watchCh := make(chan bool) 72 go func() { 73 for { 74 if watch.Next() { 75 select { 76 case <-w.tomb.Dying(): 77 return 78 case watchCh <- true: 79 } 80 } else { 81 // watcher or voyeur.Value closed. 82 close(watchCh) 83 return 84 } 85 } 86 }() 87 88 for { 89 // Always unconditionally check for a change in API addresses 90 // first, in case there was a change between the start func 91 // and the call to Watch. 92 if !stringSliceEq(w.addrs, getAPIAddresses(w.agent)) { 93 logger.Debugf("API addresses changed in agent config") 94 return dependency.ErrBounce 95 } 96 97 select { 98 case <-w.tomb.Dying(): 99 return tomb.ErrDying 100 case _, ok := <-watchCh: 101 if !ok { 102 return errors.New("config changed value closed") 103 } 104 } 105 } 106 } 107 108 // Kill implements worker.Worker. 109 func (w *apiconfigwatcher) Kill() { 110 w.tomb.Kill(nil) 111 } 112 113 // Wait implements worker.Worker. 114 func (w *apiconfigwatcher) Wait() error { 115 return w.tomb.Wait() 116 } 117 118 func getAPIAddresses(a agent.Agent) []string { 119 config := a.CurrentConfig() 120 addrs, err := config.APIAddresses() 121 if err != nil { 122 logger.Errorf("retrieving API addresses: %s", err) 123 addrs = nil 124 } 125 sort.Strings(addrs) 126 return addrs 127 } 128 129 func stringSliceEq(a, b []string) bool { 130 if a == nil && b == nil { 131 return true 132 } 133 134 if a == nil || b == nil { 135 return false 136 } 137 138 if len(a) != len(b) { 139 return false 140 } 141 142 for i := range a { 143 if a[i] != b[i] { 144 return false 145 } 146 } 147 return true 148 }