github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/remoterelations/remoterelations.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package remoterelations 5 6 import ( 7 "io" 8 "time" 9 10 "github.com/juju/clock" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "gopkg.in/juju/names.v2" 14 "gopkg.in/juju/worker.v1" 15 "gopkg.in/juju/worker.v1/catacomb" 16 "gopkg.in/macaroon.v2-unstable" 17 18 "github.com/juju/juju/api" 19 "github.com/juju/juju/apiserver/params" 20 "github.com/juju/juju/core/status" 21 "github.com/juju/juju/core/watcher" 22 ) 23 24 var logger = loggo.GetLogger("juju.worker.remoterelations") 25 26 // RemoteModelRelationsFacadeCloser implements RemoteModelRelationsFacade 27 // and add a Close() method. 28 type RemoteModelRelationsFacadeCloser interface { 29 io.Closer 30 RemoteModelRelationsFacade 31 } 32 33 // RemoteModelRelationsFacade instances publish local relation changes to the 34 // model hosting the remote application involved in the relation, and also watches 35 // for remote relation changes which are then pushed to the local model. 36 type RemoteModelRelationsFacade interface { 37 // RegisterRemoteRelations sets up the remote model to participate 38 // in the specified relations. 39 RegisterRemoteRelations(relations ...params.RegisterRemoteRelationArg) ([]params.RegisterRemoteRelationResult, error) 40 41 // PublishRelationChange publishes relation changes to the 42 // model hosting the remote application involved in the relation. 43 PublishRelationChange(params.RemoteRelationChangeEvent) error 44 45 // WatchRelationUnits returns a watcher that notifies of changes to the 46 // units in the remote model for the relation with the given remote token. 47 WatchRelationUnits(arg params.RemoteEntityArg) (watcher.RelationUnitsWatcher, error) 48 49 // RelationUnitSettings returns the relation unit settings for the given relation units in the remote model. 50 RelationUnitSettings([]params.RemoteRelationUnit) ([]params.SettingsResult, error) 51 52 // WatchRelationSuspendedStatus starts a RelationStatusWatcher for watching the 53 // relations of each specified application in the remote model. 54 WatchRelationSuspendedStatus(arg params.RemoteEntityArg) (watcher.RelationStatusWatcher, error) 55 56 // WatchOfferStatus starts an OfferStatusWatcher for watching the status 57 // of the specified offer in the remote model. 58 WatchOfferStatus(arg params.OfferArg) (watcher.OfferStatusWatcher, error) 59 } 60 61 // RemoteRelationsFacade exposes remote relation functionality to a worker. 62 type RemoteRelationsFacade interface { 63 // ImportRemoteEntity adds an entity to the remote entities collection 64 // with the specified opaque token. 65 ImportRemoteEntity(entity names.Tag, token string) error 66 67 // SaveMacaroon saves the macaroon for the entity. 68 SaveMacaroon(entity names.Tag, mac *macaroon.Macaroon) error 69 70 // ExportEntities allocates unique, remote entity IDs for the 71 // given entities in the local model. 72 ExportEntities([]names.Tag) ([]params.TokenResult, error) 73 74 // GetToken returns the token associated with the entity with the given tag. 75 GetToken(names.Tag) (string, error) 76 77 // RelationUnitSettings returns the relation unit settings for the 78 // given relation units in the local model. 79 RelationUnitSettings([]params.RelationUnit) ([]params.SettingsResult, error) 80 81 // Relations returns information about the relations 82 // with the specified keys in the local model. 83 Relations(keys []string) ([]params.RemoteRelationResult, error) 84 85 // RemoteApplications returns the current state of the remote applications with 86 // the specified names in the local model. 87 RemoteApplications(names []string) ([]params.RemoteApplicationResult, error) 88 89 // WatchLocalRelationUnits returns a watcher that notifies of changes to the 90 // local units in the relation with the given key. 91 WatchLocalRelationUnits(relationKey string) (watcher.RelationUnitsWatcher, error) 92 93 // WatchRemoteApplications watches for addition, removal and lifecycle 94 // changes to remote applications known to the local model. 95 WatchRemoteApplications() (watcher.StringsWatcher, error) 96 97 // WatchRemoteApplicationRelations starts a StringsWatcher for watching the relations of 98 // each specified application in the local model, and returns the watcher IDs 99 // and initial values, or an error if the application's relations could not be 100 // watched. 101 WatchRemoteApplicationRelations(application string) (watcher.StringsWatcher, error) 102 103 // ConsumeRemoteRelationChange consumes a change to settings originating 104 // from the remote/offering side of a relation. 105 ConsumeRemoteRelationChange(change params.RemoteRelationChangeEvent) error 106 107 // ControllerAPIInfoForModel returns the controller api info for a model. 108 ControllerAPIInfoForModel(modelUUID string) (*api.Info, error) 109 110 // SetRemoteApplicationStatus sets the status for the specified remote application. 111 SetRemoteApplicationStatus(applicationName string, status status.Status, message string) error 112 } 113 114 type newRemoteRelationsFacadeFunc func(*api.Info) (RemoteModelRelationsFacadeCloser, error) 115 116 // Config defines the operation of a Worker. 117 type Config struct { 118 ModelUUID string 119 RelationsFacade RemoteRelationsFacade 120 NewRemoteModelFacadeFunc newRemoteRelationsFacadeFunc 121 Clock clock.Clock 122 } 123 124 // Validate returns an error if config cannot drive a Worker. 125 func (config Config) Validate() error { 126 if config.ModelUUID == "" { 127 return errors.NotValidf("empty model uuid") 128 } 129 if config.RelationsFacade == nil { 130 return errors.NotValidf("nil Facade") 131 } 132 if config.NewRemoteModelFacadeFunc == nil { 133 return errors.NotValidf("nil Remote Model Facade func") 134 } 135 if config.Clock == nil { 136 return errors.NotValidf("nil Clock") 137 } 138 return nil 139 } 140 141 // New returns a Worker backed by config, or an error. 142 func New(config Config) (*Worker, error) { 143 if err := config.Validate(); err != nil { 144 return nil, errors.Trace(err) 145 } 146 147 w := &Worker{ 148 config: config, 149 logger: logger, 150 runner: worker.NewRunner(worker.RunnerParams{ 151 Clock: config.Clock, 152 153 // One of the remote application workers failing should not 154 // prevent the others from running. 155 IsFatal: func(error) bool { return false }, 156 157 // For any failures, try again in 1 minute. 158 RestartDelay: time.Minute, 159 }), 160 } 161 err := catacomb.Invoke(catacomb.Plan{ 162 Site: &w.catacomb, 163 Work: w.loop, 164 Init: []worker.Worker{w.runner}, 165 }) 166 return w, errors.Trace(err) 167 } 168 169 // Worker manages relations and associated settings where 170 // one end of the relation is a remote application. 171 type Worker struct { 172 catacomb catacomb.Catacomb 173 config Config 174 logger loggo.Logger 175 176 runner *worker.Runner 177 } 178 179 // Kill is defined on worker.Worker. 180 func (w *Worker) Kill() { 181 w.catacomb.Kill(nil) 182 } 183 184 // Wait is defined on worker.Worker. 185 func (w *Worker) Wait() error { 186 return w.catacomb.Wait() 187 } 188 189 func (w *Worker) loop() (err error) { 190 changes, err := w.config.RelationsFacade.WatchRemoteApplications() 191 if err != nil { 192 return errors.Trace(err) 193 } 194 if err := w.catacomb.Add(changes); err != nil { 195 return errors.Trace(err) 196 } 197 for { 198 select { 199 case <-w.catacomb.Dying(): 200 return w.catacomb.ErrDying() 201 case applicationIds, ok := <-changes.Changes(): 202 if !ok { 203 return errors.New("change channel closed") 204 } 205 err = w.handleApplicationChanges(applicationIds) 206 if err != nil { 207 return err 208 } 209 } 210 } 211 } 212 213 func (w *Worker) handleApplicationChanges(applicationIds []string) error { 214 // TODO(wallyworld) - watcher should not give empty events 215 if len(applicationIds) == 0 { 216 return nil 217 } 218 logger.Debugf("processing remote application changes for: %s", applicationIds) 219 220 // Fetch the current state of each of the remote applications that have changed. 221 results, err := w.config.RelationsFacade.RemoteApplications(applicationIds) 222 if err != nil { 223 return errors.Annotate(err, "querying remote applications") 224 } 225 226 for i, result := range results { 227 name := applicationIds[i] 228 if result.Error != nil { 229 // The the remote application has been removed, stop its worker. 230 if params.IsCodeNotFound(result.Error) { 231 if err := w.runner.StopWorker(name); err != nil { 232 return err 233 } 234 continue 235 } 236 return errors.Annotatef(err, "querying remote application %q", name) 237 } 238 if _, err := w.runner.Worker(name, w.catacomb.Dying()); err == nil { 239 // TODO(wallyworld): handle application dying or dead. 240 // As of now, if the worker is already running, that's all we need. 241 continue 242 } 243 244 remoteApp := *result.Result 245 startFunc := func() (worker.Worker, error) { 246 appWorker := &remoteApplicationWorker{ 247 offerUUID: remoteApp.OfferUUID, 248 applicationName: remoteApp.Name, 249 localModelUUID: w.config.ModelUUID, 250 remoteModelUUID: remoteApp.ModelUUID, 251 isConsumerProxy: remoteApp.IsConsumerProxy, 252 offerMacaroon: remoteApp.Macaroon, 253 localRelationChanges: make(chan params.RemoteRelationChangeEvent), 254 remoteRelationChanges: make(chan params.RemoteRelationChangeEvent), 255 localModelFacade: w.config.RelationsFacade, 256 newRemoteModelRelationsFacadeFunc: w.config.NewRemoteModelFacadeFunc, 257 } 258 if err := catacomb.Invoke(catacomb.Plan{ 259 Site: &appWorker.catacomb, 260 Work: appWorker.loop, 261 }); err != nil { 262 return nil, errors.Trace(err) 263 } 264 return appWorker, nil 265 } 266 267 logger.Debugf("starting watcher for remote application %q", name) 268 // Start the application worker to watch for things like new relations. 269 if err := w.runner.StartWorker(name, startFunc); err != nil { 270 return errors.Annotate(err, "error starting remote application worker") 271 } 272 } 273 return nil 274 }