github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "fmt" 8 "io" 9 "sync" 10 "time" 11 12 "github.com/juju/clock" 13 "github.com/juju/errors" 14 "github.com/juju/loggo" 15 "github.com/juju/names/v5" 16 "github.com/juju/worker/v3" 17 "github.com/juju/worker/v3/catacomb" 18 "gopkg.in/macaroon.v2" 19 20 "github.com/juju/juju/api" 21 apiwatcher "github.com/juju/juju/api/watcher" 22 "github.com/juju/juju/core/crossmodel" 23 "github.com/juju/juju/core/life" 24 "github.com/juju/juju/core/status" 25 "github.com/juju/juju/core/watcher" 26 "github.com/juju/juju/rpc/params" 27 ) 28 29 // logger is here to stop the desire of creating a package level logger. 30 // Don't do this, instead use the one passed as manifold config. 31 type logger interface{} 32 33 var _ logger = struct{}{} 34 35 // RemoteModelRelationsFacadeCloser implements RemoteModelRelationsFacade 36 // and add a Close() method. 37 type RemoteModelRelationsFacadeCloser interface { 38 io.Closer 39 RemoteModelRelationsFacade 40 } 41 42 // RemoteModelRelationsFacade instances publish local relation changes to the 43 // model hosting the remote application involved in the relation, and also watches 44 // for remote relation changes which are then pushed to the local model. 45 type RemoteModelRelationsFacade interface { 46 // RegisterRemoteRelations sets up the remote model to participate 47 // in the specified relations. 48 RegisterRemoteRelations(relations ...params.RegisterRemoteRelationArg) ([]params.RegisterRemoteRelationResult, error) 49 50 // PublishRelationChange publishes relation changes to the 51 // model hosting the remote application involved in the relation. 52 PublishRelationChange(params.RemoteRelationChangeEvent) error 53 54 // WatchRelationChanges returns a watcher that notifies of changes 55 // to the units in the remote model for the relation with the 56 // given remote token. We need to pass the application token for 57 // the case where we're talking to a v1 API and the client needs 58 // to convert RelationUnitsChanges into RemoteRelationChangeEvents 59 // as they come in. 60 WatchRelationChanges(relationToken, applicationToken string, macs macaroon.Slice) (apiwatcher.RemoteRelationWatcher, error) 61 62 // WatchRelationSuspendedStatus starts a RelationStatusWatcher for watching the 63 // relations of each specified application in the remote model. 64 WatchRelationSuspendedStatus(arg params.RemoteEntityArg) (watcher.RelationStatusWatcher, error) 65 66 // WatchOfferStatus starts an OfferStatusWatcher for watching the status 67 // of the specified offer in the remote model. 68 WatchOfferStatus(arg params.OfferArg) (watcher.OfferStatusWatcher, error) 69 70 // WatchConsumedSecretsChanges starts a watcher for any changes to secrets 71 // consumed by the specified application. 72 WatchConsumedSecretsChanges(applicationToken, relationToken string, mac *macaroon.Macaroon) (watcher.SecretsRevisionWatcher, error) 73 } 74 75 // RemoteRelationsFacade exposes remote relation functionality to a worker. 76 type RemoteRelationsFacade interface { 77 // ImportRemoteEntity adds an entity to the remote entities collection 78 // with the specified opaque token. 79 ImportRemoteEntity(entity names.Tag, token string) error 80 81 // SaveMacaroon saves the macaroon for the entity. 82 SaveMacaroon(entity names.Tag, mac *macaroon.Macaroon) error 83 84 // ExportEntities allocates unique, remote entity IDs for the 85 // given entities in the local model. 86 ExportEntities([]names.Tag) ([]params.TokenResult, error) 87 88 // GetToken returns the token associated with the entity with the given tag. 89 GetToken(names.Tag) (string, error) 90 91 // Relations returns information about the relations 92 // with the specified keys in the local model. 93 Relations(keys []string) ([]params.RemoteRelationResult, error) 94 95 // RemoteApplications returns the current state of the remote applications with 96 // the specified names in the local model. 97 RemoteApplications(names []string) ([]params.RemoteApplicationResult, error) 98 99 // WatchLocalRelationChanges returns a watcher that notifies of changes to the 100 // local units in the relation with the given key. 101 WatchLocalRelationChanges(relationKey string) (apiwatcher.RemoteRelationWatcher, error) 102 103 // WatchRemoteApplications watches for addition, removal and lifecycle 104 // changes to remote applications known to the local model. 105 WatchRemoteApplications() (watcher.StringsWatcher, error) 106 107 // WatchRemoteApplicationRelations starts a StringsWatcher for watching the relations of 108 // each specified application in the local model, and returns the watcher IDs 109 // and initial values, or an error if the application's relations could not be 110 // watched. 111 WatchRemoteApplicationRelations(application string) (watcher.StringsWatcher, error) 112 113 // ConsumeRemoteRelationChange consumes a change to settings originating 114 // from the remote/offering side of a relation. 115 ConsumeRemoteRelationChange(change params.RemoteRelationChangeEvent) error 116 117 // ControllerAPIInfoForModel returns the controller api info for a model. 118 ControllerAPIInfoForModel(modelUUID string) (*api.Info, error) 119 120 // SetRemoteApplicationStatus sets the status for the specified remote application. 121 SetRemoteApplicationStatus(applicationName string, status status.Status, message string) error 122 123 // UpdateControllerForModel ensures that there is an external controller record 124 // for the input info, associated with the input model ID. 125 UpdateControllerForModel(controller crossmodel.ControllerInfo, modelUUID string) error 126 127 // ConsumeRemoteSecretChanges updates the local model with secret revision changes 128 // originating from the remote/offering model. 129 ConsumeRemoteSecretChanges(changes []watcher.SecretRevisionChange) error 130 } 131 132 type newRemoteRelationsFacadeFunc func(*api.Info) (RemoteModelRelationsFacadeCloser, error) 133 134 // Config defines the operation of a Worker. 135 type Config struct { 136 ModelUUID string 137 RelationsFacade RemoteRelationsFacade 138 NewRemoteModelFacadeFunc newRemoteRelationsFacadeFunc 139 Clock clock.Clock 140 Logger Logger 141 142 // Used for testing. 143 Runner *worker.Runner 144 } 145 146 // Validate returns an error if config cannot drive a Worker. 147 func (config Config) Validate() error { 148 if config.ModelUUID == "" { 149 return errors.NotValidf("empty model uuid") 150 } 151 if config.RelationsFacade == nil { 152 return errors.NotValidf("nil Facade") 153 } 154 if config.NewRemoteModelFacadeFunc == nil { 155 return errors.NotValidf("nil Remote Model Facade func") 156 } 157 if config.Clock == nil { 158 return errors.NotValidf("nil Clock") 159 } 160 if config.Logger == nil { 161 return errors.NotValidf("nil Logger") 162 } 163 return nil 164 } 165 166 // New returns a Worker backed by config, or an error. 167 func New(config Config) (*Worker, error) { 168 if err := config.Validate(); err != nil { 169 return nil, errors.Trace(err) 170 } 171 172 runner := config.Runner 173 if runner == nil { 174 runner = worker.NewRunner(worker.RunnerParams{ 175 Clock: config.Clock, 176 Logger: config.Logger, 177 178 // One of the remote application workers failing should not 179 // prevent the others from running. 180 IsFatal: func(error) bool { return false }, 181 182 // For any failures, try again in 15 seconds. 183 RestartDelay: 15 * time.Second, 184 }) 185 } 186 w := &Worker{ 187 config: config, 188 offerUUIDs: make(map[string]string), 189 runner: runner, 190 } 191 err := catacomb.Invoke(catacomb.Plan{ 192 Site: &w.catacomb, 193 Work: w.loop, 194 Init: []worker.Worker{w.runner}, 195 }) 196 return w, errors.Trace(err) 197 } 198 199 // Worker manages relations and associated settings where 200 // one end of the relation is a remote application. 201 type Worker struct { 202 catacomb catacomb.Catacomb 203 config Config 204 logger loggo.Logger 205 206 runner *worker.Runner 207 mu sync.Mutex 208 209 // offerUUIDs records the offer UUID used for each saas name. 210 offerUUIDs map[string]string 211 } 212 213 // Kill is defined on worker.Worker. 214 func (w *Worker) Kill() { 215 w.catacomb.Kill(nil) 216 } 217 218 // Wait is defined on worker.Worker. 219 func (w *Worker) Wait() error { 220 err := w.catacomb.Wait() 221 if err != nil { 222 w.logger.Errorf("error in top level remote relations worker: %v", err) 223 } 224 return err 225 } 226 227 func (w *Worker) loop() (err error) { 228 changes, err := w.config.RelationsFacade.WatchRemoteApplications() 229 if err != nil { 230 return errors.Trace(err) 231 } 232 if err := w.catacomb.Add(changes); err != nil { 233 return errors.Trace(err) 234 } 235 for { 236 select { 237 case <-w.catacomb.Dying(): 238 return w.catacomb.ErrDying() 239 case applicationIds, ok := <-changes.Changes(): 240 if !ok { 241 return errors.New("change channel closed") 242 } 243 err = w.handleApplicationChanges(applicationIds) 244 if err != nil { 245 return err 246 } 247 } 248 } 249 } 250 251 func (w *Worker) handleApplicationChanges(applicationIds []string) error { 252 w.mu.Lock() 253 defer w.mu.Unlock() 254 255 // TODO(wallyworld) - watcher should not give empty events 256 if len(applicationIds) == 0 { 257 return nil 258 } 259 logger := w.config.Logger 260 logger.Debugf("processing remote application changes for: %s", applicationIds) 261 262 // Fetch the current state of each of the remote applications that have changed. 263 results, err := w.config.RelationsFacade.RemoteApplications(applicationIds) 264 if err != nil { 265 return errors.Annotate(err, "querying remote applications") 266 } 267 268 for i, result := range results { 269 name := applicationIds[i] 270 271 // The remote application may refer to an offer that has been removed from 272 // the offering model, or it may refer to a new offer with a different UUID. 273 // If it is for a new offer, we need to stop any current worker for the old offer. 274 appGone := result.Error != nil && params.IsCodeNotFound(result.Error) 275 if result.Error != nil && !appGone { 276 return errors.Annotatef(result.Error, "querying remote application %q", name) 277 } 278 279 var remoteApp *params.RemoteApplication 280 offerChanged := false 281 if !appGone { 282 remoteApp = result.Result 283 existingOfferUUID, ok := w.offerUUIDs[result.Result.Name] 284 appGone = remoteApp.Status == string(status.Terminated) || remoteApp.Life == life.Dead 285 offerChanged = ok && existingOfferUUID != result.Result.OfferUUID 286 } 287 if appGone || offerChanged { 288 // The remote application has been removed, stop its worker. 289 logger.Debugf("saas application %q gone from offering model", name) 290 err := w.runner.StopAndRemoveWorker(name, w.catacomb.Dying()) 291 if err != nil && !errors.IsNotFound(err) { 292 w.logger.Warningf("error stopping saas worker for %q: %v", name, err) 293 } 294 delete(w.offerUUIDs, name) 295 if appGone { 296 continue 297 } 298 } 299 300 startFunc := func() (worker.Worker, error) { 301 appWorker := &remoteApplicationWorker{ 302 offerUUID: remoteApp.OfferUUID, 303 applicationName: remoteApp.Name, 304 localModelUUID: w.config.ModelUUID, 305 remoteModelUUID: remoteApp.ModelUUID, 306 isConsumerProxy: remoteApp.IsConsumerProxy, 307 consumeVersion: remoteApp.ConsumeVersion, 308 offerMacaroon: remoteApp.Macaroon, 309 localRelationUnitChanges: make(chan RelationUnitChangeEvent), 310 remoteRelationUnitChanges: make(chan RelationUnitChangeEvent), 311 localModelFacade: w.config.RelationsFacade, 312 newRemoteModelRelationsFacadeFunc: w.config.NewRemoteModelFacadeFunc, 313 logger: logger, 314 } 315 if err := catacomb.Invoke(catacomb.Plan{ 316 Site: &appWorker.catacomb, 317 Work: appWorker.loop, 318 }); err != nil { 319 return nil, errors.Trace(err) 320 } 321 return appWorker, nil 322 } 323 324 logger.Debugf("starting watcher for remote application %q", name) 325 // Start the application worker to watch for things like new relations. 326 w.offerUUIDs[name] = remoteApp.OfferUUID 327 if err := w.runner.StartWorker(name, startFunc); err != nil { 328 if errors.IsAlreadyExists(err) { 329 w.logger.Debugf("already running remote application worker for %q", name) 330 } else if err != nil { 331 return errors.Annotate(err, "error starting remote application worker") 332 } 333 } 334 w.offerUUIDs[name] = remoteApp.OfferUUID 335 } 336 return nil 337 } 338 339 // Report provides information for the engine report. 340 func (w *Worker) Report() map[string]interface{} { 341 result := make(map[string]interface{}) 342 w.mu.Lock() 343 defer w.mu.Unlock() 344 345 saasWorkers := make(map[string]interface{}) 346 for name := range w.offerUUIDs { 347 appWorker, err := w.runner.Worker(name, w.catacomb.Dying()) 348 if err != nil { 349 saasWorkers[name] = fmt.Sprintf("ERROR: %v", err) 350 continue 351 } 352 saasWorkers[name] = appWorker.(worker.Reporter).Report() 353 } 354 result["workers"] = saasWorkers 355 return result 356 }