github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/presence/presence.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package presence 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 "github.com/juju/pubsub" 11 "gopkg.in/juju/worker.v1" 12 "gopkg.in/tomb.v2" 13 14 "github.com/juju/juju/core/presence" 15 "github.com/juju/juju/pubsub/apiserver" 16 "github.com/juju/juju/pubsub/forwarder" 17 ) 18 19 // WorkerConfig defines the configuration values that the pubsub worker needs 20 // to operate. 21 type WorkerConfig struct { 22 Origin string 23 Hub *pubsub.StructuredHub 24 Recorder presence.Recorder 25 Logger Logger 26 } 27 28 // Validate ensures that the required values are set in the structure. 29 func (c *WorkerConfig) Validate() error { 30 if c.Origin == "" { 31 return errors.NotValidf("missing origin") 32 } 33 if c.Hub == nil { 34 return errors.NotValidf("missing hub") 35 } 36 if c.Recorder == nil { 37 return errors.NotValidf("missing recorder") 38 } 39 if c.Logger == nil { 40 return errors.NotValidf("missing logger") 41 } 42 return nil 43 } 44 45 // NewWorker creates a new presence worker that responds to pubsub connection 46 // messages. 47 func NewWorker(config WorkerConfig) (worker.Worker, error) { 48 if err := config.Validate(); err != nil { 49 return nil, errors.Trace(err) 50 } 51 // Don't return from NewWorker until the loop has started and 52 // has subscribed to everything. 53 started := make(chan struct{}) 54 w := &wrapper{ 55 origin: config.Origin, 56 hub: config.Hub, 57 recorder: config.Recorder, 58 logger: config.Logger, 59 } 60 w.tomb.Go(func() error { 61 return w.loop(started) 62 }) 63 select { 64 case <-started: 65 case <-time.After(10 * time.Second): 66 return nil, errors.New("worker failed to start properly") 67 } 68 return w, nil 69 } 70 71 type wrapper struct { 72 tomb tomb.Tomb 73 origin string 74 hub *pubsub.StructuredHub 75 recorder presence.Recorder 76 logger Logger 77 } 78 79 // Report implements worker.Report. 80 func (w *wrapper) Report() map[string]interface{} { 81 all := w.recorder.Connections() 82 result := make(map[string]interface{}) 83 servers := all.Servers() 84 for _, name := range servers { 85 conns := all.ForServer(name) 86 result[name] = conns.Count() 87 } 88 return result 89 } 90 91 func (w *wrapper) loop(started chan struct{}) error { 92 multiplexer, err := w.hub.NewMultiplexer() 93 if err != nil { 94 return errors.Trace(err) 95 } 96 defer multiplexer.Unsubscribe() 97 98 if err := multiplexer.Add(forwarder.ConnectedTopic, w.forwarderConnect); err != nil { 99 return errors.Trace(err) 100 } 101 if err := multiplexer.Add(forwarder.DisconnectedTopic, w.forwarderDisconnect); err != nil { 102 return errors.Trace(err) 103 } 104 if err := multiplexer.Add(apiserver.ConnectTopic, w.agentLogin); err != nil { 105 return errors.Trace(err) 106 } 107 if err := multiplexer.Add(apiserver.DisconnectTopic, w.agentDisconnect); err != nil { 108 return errors.Trace(err) 109 } 110 if err := multiplexer.Add(apiserver.PresenceRequestTopic, w.presenceRequest); err != nil { 111 return errors.Trace(err) 112 } 113 if err := multiplexer.Add(apiserver.PresenceResponseTopic, w.presenceResponse); err != nil { 114 return errors.Trace(err) 115 } 116 // Let the caller know we are done. 117 close(started) 118 // Don't exit until we are told to. Exiting unsubscribes. 119 <-w.tomb.Dying() 120 w.logger.Tracef("presence loop finished") 121 return nil 122 } 123 124 func (w *wrapper) forwarderConnect(topic string, data forwarder.OriginTarget, err error) { 125 if err != nil { 126 w.logger.Errorf("forwarderConnect error %v", err) 127 return 128 } 129 130 // If we have just set up forwarding to another server, or another server 131 // has just set up forwarding to us, ask for their presence info. 132 w.logger.Tracef("forwarding connection up for %s -> %s", data.Origin, data.Target) 133 var request string 134 switch { 135 case data.Origin == w.origin: 136 request = data.Target 137 case data.Target == w.origin: 138 request = data.Origin 139 default: 140 return 141 } 142 w.logger.Tracef("request presence info from %s", request) 143 msg := apiserver.OriginTarget{Target: request} 144 w.hub.Publish(apiserver.PresenceRequestTopic, msg) 145 w.logger.Tracef("request sent") 146 } 147 148 func (w *wrapper) forwarderDisconnect(topic string, data forwarder.OriginTarget, err error) { 149 if err != nil { 150 w.logger.Errorf("forwarderChange error %v", err) 151 return 152 } 153 // If we have lost connectivity to the target, we mark the server down. 154 // Ideally this would be when the target is no longer forwarding us messages, 155 // but we aren't guaranteed to get those messages, so we use the lack of our 156 // connectivity to the other machine to indicate that comms are down. 157 if data.Origin == w.origin { 158 w.logger.Tracef("forwarding connection down for %s", data.Target) 159 w.recorder.ServerDown(data.Target) 160 } 161 } 162 163 func (w *wrapper) agentLogin(topic string, data apiserver.APIConnection, err error) { 164 if err != nil { 165 w.logger.Errorf("agentLogin error %v", err) 166 return 167 } 168 if w.logger.IsTraceEnabled() { 169 agentName := data.AgentTag 170 if data.ControllerAgent { 171 agentName += " (controller)" 172 } 173 w.logger.Tracef("api connect %s:%s -> %s (%v)", data.ModelUUID, agentName, data.Origin, data.ConnectionID) 174 } 175 w.recorder.Connect(data.Origin, data.ModelUUID, data.AgentTag, data.ConnectionID, data.ControllerAgent, data.UserData) 176 } 177 178 func (w *wrapper) agentDisconnect(topic string, data apiserver.APIConnection, err error) { 179 if err != nil { 180 w.logger.Errorf("agentDisconnect error %v", err) 181 return 182 } 183 w.logger.Tracef("api disconnect %s (%v)", data.Origin, data.ConnectionID) 184 w.recorder.Disconnect(data.Origin, data.ConnectionID) 185 } 186 187 func (w *wrapper) presenceRequest(topic string, data apiserver.OriginTarget, err error) { 188 if err != nil { 189 w.logger.Errorf("connectionChange error %v", err) 190 return 191 } 192 // If the message is not meant for us, ignore. 193 if data.Target != w.origin { 194 w.logger.Tracef("presence request for %s ignored, as we are %s", data.Target, w.origin) 195 return 196 } 197 198 w.logger.Tracef("presence request from %s", data.Origin) 199 200 connections := w.recorder.Connections().ForServer(w.origin) 201 values := connections.Values() 202 response := apiserver.PresenceResponse{ 203 Connections: make([]apiserver.APIConnection, len(values)), 204 } 205 for i, value := range values { 206 if value.Status != presence.Alive { 207 w.logger.Infof("presence response has weird status: %#v", value) 208 } 209 response.Connections[i] = apiserver.APIConnection{ 210 AgentTag: value.Agent, 211 ControllerAgent: value.ControllerAgent, 212 ModelUUID: value.Model, 213 ConnectionID: value.ConnectionID, 214 Origin: value.Server, 215 UserData: value.UserData, 216 } 217 } 218 _, err = w.hub.Publish(apiserver.PresenceResponseTopic, response) 219 if err != nil { 220 w.logger.Errorf("cannot send presence response: %v", err) 221 } 222 } 223 224 func (w *wrapper) presenceResponse(topic string, data apiserver.PresenceResponse, err error) { 225 if err != nil { 226 w.logger.Errorf("connectionChange error %v", err) 227 return 228 } 229 // If this message is from us, ignore it. 230 if data.Origin == w.origin { 231 w.logger.Tracef("ignoring our own presence response message") 232 return 233 } 234 235 // Build up a slice of presence values so we can transactionally 236 // update the recorder. 237 values := make([]presence.Value, 0, len(data.Connections)) 238 for _, conn := range data.Connections { 239 if w.logger.IsTraceEnabled() { 240 agentName := conn.AgentTag 241 if conn.ControllerAgent { 242 agentName += " (controller)" 243 } 244 w.logger.Tracef("setting presence %s:%s -> %s (%v)", conn.ModelUUID, agentName, conn.Origin, conn.ConnectionID) 245 } 246 values = append(values, presence.Value{ 247 Model: conn.ModelUUID, 248 Server: conn.Origin, 249 Agent: conn.AgentTag, 250 ConnectionID: conn.ConnectionID, 251 ControllerAgent: conn.ControllerAgent, 252 UserData: conn.UserData, 253 }) 254 } 255 256 err = w.recorder.UpdateServer(data.Origin, values) 257 // An error here is only if the values don't come from the server. 258 // This would be a programming error, and as such, we just log it. 259 if err != nil { 260 w.logger.Errorf("UpdateServer error %v", err) 261 } 262 } 263 264 // Kill implements Worker.Kill(). 265 func (w *wrapper) Kill() { 266 w.tomb.Kill(nil) 267 } 268 269 // Wait implements Worker.Wait(). 270 func (w *wrapper) Wait() error { 271 return w.tomb.Wait() 272 }