github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/pubsub/subscriber.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package pubsub 5 6 import ( 7 "fmt" 8 "sort" 9 "strings" 10 "sync" 11 12 "github.com/juju/clock" 13 "github.com/juju/collections/set" 14 "github.com/juju/errors" 15 "github.com/juju/pubsub" 16 "gopkg.in/juju/names.v2" 17 "gopkg.in/juju/worker.v1" 18 "gopkg.in/juju/worker.v1/catacomb" 19 20 "github.com/juju/juju/api" 21 "github.com/juju/juju/apiserver/params" 22 "github.com/juju/juju/pubsub/apiserver" 23 ) 24 25 // WorkerConfig defines the configuration values that the pubsub worker needs 26 // to operate. 27 type WorkerConfig struct { 28 Origin string 29 Clock clock.Clock 30 Hub *pubsub.StructuredHub 31 Logger Logger 32 33 APIInfo *api.Info 34 NewWriter func(*api.Info) (MessageWriter, error) 35 NewRemote func(RemoteServerConfig) (RemoteServer, error) 36 } 37 38 // Validate checks that all the values have been set. 39 func (c *WorkerConfig) Validate() error { 40 if c.Origin == "" { 41 return errors.NotValidf("missing origin") 42 } 43 if c.Clock == nil { 44 return errors.NotValidf("missing clock") 45 } 46 if c.Hub == nil { 47 return errors.NotValidf("missing hub") 48 } 49 if c.Logger == nil { 50 return errors.NotValidf("missing logger") 51 } 52 if c.APIInfo == nil { 53 return errors.NotValidf("missing api info") 54 } 55 if c.NewWriter == nil { 56 return errors.NotValidf("missing new writer") 57 } 58 if c.NewRemote == nil { 59 return errors.NotValidf("missing new remote") 60 } 61 return nil 62 } 63 64 // NewWorker exposes the subscriber as a Worker. 65 func NewWorker(config WorkerConfig) (worker.Worker, error) { 66 return newSubscriber(config) 67 } 68 69 type subscriber struct { 70 config WorkerConfig 71 catacomb catacomb.Catacomb 72 73 unsubAll func() 74 unsubServerDetails func() 75 76 // servers represent connections to each of the other api servers. 77 servers map[string]RemoteServer 78 mutex sync.Mutex 79 } 80 81 func newSubscriber(config WorkerConfig) (*subscriber, error) { 82 if err := config.Validate(); err != nil { 83 return nil, errors.Trace(err) 84 } 85 sub := &subscriber{ 86 config: config, 87 servers: make(map[string]RemoteServer), 88 } 89 unsub, err := config.Hub.SubscribeMatch(pubsub.MatchAll, sub.forwardMessage) 90 if err != nil { 91 return nil, errors.Trace(err) 92 } 93 sub.unsubAll = unsub 94 config.Logger.Debugf("subscribing to details topic") 95 unsub, err = config.Hub.Subscribe(apiserver.DetailsTopic, sub.apiServerChanges) 96 if err != nil { 97 return nil, errors.Trace(err) 98 } 99 sub.unsubServerDetails = unsub 100 101 // Ask for the current server details now that we're subscribed. 102 detailsRequest := apiserver.DetailsRequest{ 103 Requester: "pubsub-forwarder", 104 LocalOnly: true, 105 } 106 if _, err := config.Hub.Publish(apiserver.DetailsRequestTopic, detailsRequest); err != nil { 107 return nil, errors.Trace(err) 108 } 109 110 err = catacomb.Invoke(catacomb.Plan{ 111 Site: &sub.catacomb, 112 Work: sub.waitForDeath, 113 }) 114 if err != nil { 115 return nil, errors.Trace(err) 116 } 117 118 return sub, nil 119 } 120 121 // Report returns the same information as the introspection report 122 // but in the map for for the dependency engine report. 123 func (s *subscriber) Report() map[string]interface{} { 124 s.mutex.Lock() 125 defer s.mutex.Unlock() 126 result := map[string]interface{}{ 127 "source": s.config.Origin, 128 } 129 targets := make(map[string]interface{}) 130 for target, remote := range s.servers { 131 targets[target] = remote.Report() 132 } 133 if len(targets) > 0 { 134 result["targets"] = targets 135 } 136 return result 137 138 } 139 140 // IntrospectionReport is the method called by the introspection 141 // worker to get what to show to the user. 142 func (s *subscriber) IntrospectionReport() string { 143 s.mutex.Lock() 144 defer s.mutex.Unlock() 145 146 var result []string 147 for target, remote := range s.servers { 148 info := fmt.Sprintf("Target: %s\n%s", 149 target, remote.IntrospectionReport()) 150 result = append(result, info) 151 } 152 prefix := fmt.Sprintf("Source: %s\n\n", s.config.Origin) 153 // Sorting the result gives us consisten ordering. 154 sort.Strings(result) 155 return prefix + strings.Join(result, "\n") 156 } 157 158 func (s *subscriber) waitForDeath() error { 159 s.config.Logger.Tracef("wait for catacomb dying before unsubscribe") 160 defer s.unsubAll() 161 defer s.unsubServerDetails() 162 163 <-s.catacomb.Dying() 164 s.config.Logger.Tracef("dying now") 165 return s.catacomb.ErrDying() 166 } 167 168 func (s *subscriber) apiServerChanges(topic string, details apiserver.Details, err error) { 169 s.config.Logger.Tracef("apiServerChanges: %#v", details) 170 // Make sure we have workers for the defined details. 171 if err != nil { 172 // This should never happen. 173 s.config.Logger.Errorf("subscriber callback error: %v", err) 174 return 175 } 176 177 s.mutex.Lock() 178 defer s.mutex.Unlock() 179 apiServers := set.NewStrings() 180 for id, apiServer := range details.Servers { 181 target := names.NewMachineTag(id).String() 182 apiServers.Add(target) 183 if target == s.config.Origin { 184 // We don't need to forward messages to ourselves. 185 continue 186 } 187 188 // TODO: always use the internal address? 189 addresses := apiServer.Addresses 190 if apiServer.InternalAddress != "" { 191 addresses = []string{apiServer.InternalAddress} 192 } 193 194 server, found := s.servers[target] 195 if found { 196 s.config.Logger.Tracef("update addresses for %s to %v", target, addresses) 197 server.UpdateAddresses(addresses) 198 } else { 199 s.config.Logger.Debugf("new forwarder for %s", target) 200 newInfo := *s.config.APIInfo 201 newInfo.Addrs = addresses 202 server, err := s.config.NewRemote(RemoteServerConfig{ 203 Hub: s.config.Hub, 204 Origin: s.config.Origin, 205 Target: target, 206 Clock: s.config.Clock, 207 Logger: s.config.Logger, 208 APIInfo: &newInfo, 209 NewWriter: s.config.NewWriter, 210 }) 211 if err != nil { 212 s.config.Logger.Errorf("unable to add new remote server for %q, %v", target, err) 213 continue 214 } 215 s.servers[target] = server 216 s.catacomb.Add(server) 217 } 218 } 219 for name, server := range s.servers { 220 if !apiServers.Contains(name) { 221 s.config.Logger.Debugf("%s no longer listed as an apiserver", name) 222 server.Kill() 223 err := server.Wait() 224 if err != nil { 225 s.config.Logger.Errorf("%v", err) 226 } 227 delete(s.servers, name) 228 } 229 } 230 s.config.Logger.Tracef("update complete") 231 } 232 233 func (s *subscriber) forwardMessage(topic string, data map[string]interface{}) { 234 if data["origin"] != s.config.Origin { 235 // Message does not originate from the place we care about. 236 // Nothing to do. 237 s.config.Logger.Tracef("skipping message %q as origin not ours", topic) 238 return 239 } 240 // If local-only isn't specified, then the default interface{} value is 241 // returned, which is nil, and nil isn't true. 242 if data["local-only"] == true { 243 // Local message, don't forward. 244 s.config.Logger.Tracef("skipping message %q as local-only", topic) 245 return 246 } 247 248 s.config.Logger.Tracef("forward message %q", topic) 249 message := ¶ms.PubSubMessage{Topic: topic, Data: data} 250 s.mutex.Lock() 251 defer s.mutex.Unlock() 252 for _, remote := range s.servers { 253 remote.Publish(message) 254 } 255 } 256 257 // Kill is part of the worker.Worker interface. 258 func (s *subscriber) Kill() { 259 s.catacomb.Kill(nil) 260 } 261 262 // Wait is part of the worker.Worker interface. 263 func (s *subscriber) Wait() error { 264 return s.catacomb.Wait() 265 }