github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/externalcontroller.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 9 "github.com/juju/collections/set" 10 "github.com/juju/errors" 11 "github.com/juju/mgo/v3" 12 "github.com/juju/mgo/v3/bson" 13 "github.com/juju/mgo/v3/txn" 14 "github.com/juju/names/v5" 15 16 "github.com/juju/juju/core/crossmodel" 17 ) 18 19 // ExternalController represents the state of a controller hosting 20 // other models. 21 type ExternalController interface { 22 // Id returns the external controller UUID, also used as 23 // the mongo id. 24 Id() string 25 26 // ControllerInfo returns the details required to connect to the 27 // external controller. 28 ControllerInfo() crossmodel.ControllerInfo 29 } 30 31 // externalController is an implementation of ExternalController. 32 type externalController struct { 33 doc externalControllerDoc 34 } 35 36 type externalControllerDoc struct { 37 // Id holds external controller document key. 38 // It is the controller UUID. 39 Id string `bson:"_id"` 40 41 // Alias holds an alias (human friendly) name for the controller. 42 Alias string `bson:"alias"` 43 44 // Addrs holds the host:port values for the external 45 // controller's API server. 46 Addrs []string `bson:"addresses"` 47 48 // CACert holds the certificate to validate the external 49 // controller's target API server's TLS certificate. 50 CACert string `bson:"cacert"` 51 52 // Models holds model UUIDs hosted on this controller. 53 Models []string `bson:"models"` 54 } 55 56 // newExternalControllerDoc returns a new external controller document 57 // reference representing the input controller info. 58 func newExternalControllerDoc(controller crossmodel.ControllerInfo) *externalControllerDoc { 59 return &externalControllerDoc{ 60 Id: controller.ControllerTag.Id(), 61 Alias: controller.Alias, 62 Addrs: controller.Addrs, 63 CACert: controller.CACert, 64 } 65 } 66 67 // Id implements ExternalController. 68 func (rc *externalController) Id() string { 69 return rc.doc.Id 70 } 71 72 // ControllerInfo implements ExternalController. 73 func (rc *externalController) ControllerInfo() crossmodel.ControllerInfo { 74 return crossmodel.ControllerInfo{ 75 ControllerTag: names.NewControllerTag(rc.doc.Id), 76 Alias: rc.doc.Alias, 77 Addrs: rc.doc.Addrs, 78 CACert: rc.doc.CACert, 79 } 80 } 81 82 // ExternalControllers instances provide access to external controllers in state. 83 type ExternalControllers interface { 84 Save(_ crossmodel.ControllerInfo, modelUUIDs ...string) (ExternalController, error) 85 SaveAndMoveModels(_ crossmodel.ControllerInfo, modelUUIDs ...string) error 86 Controller(controllerUUID string) (ExternalController, error) 87 ControllerForModel(modelUUID string) (ExternalController, error) 88 Remove(controllerUUID string) error 89 Watch() StringsWatcher 90 WatchController(controllerUUID string) NotifyWatcher 91 } 92 93 type externalControllers struct { 94 st *State 95 } 96 97 // NewExternalControllers creates an external controllers instance backed by a state. 98 func (s *State) NewExternalControllers() ExternalControllers { 99 return NewExternalControllers(s) 100 } 101 102 // NewExternalControllers creates an external controllers instance backed by a state. 103 func NewExternalControllers(st *State) *externalControllers { 104 return &externalControllers{st: st} 105 } 106 107 // Save creates or updates an external controller record. 108 func (ec *externalControllers) Save( 109 controller crossmodel.ControllerInfo, modelUUIDs ...string, 110 ) (ExternalController, error) { 111 if err := controller.Validate(); err != nil { 112 return nil, errors.Trace(err) 113 } 114 doc := newExternalControllerDoc(controller) 115 buildTxn := func(int) ([]txn.Op, error) { 116 ops, err := ec.upsertExternalControllerOps(doc, modelUUIDs) 117 return ops, errors.Trace(err) 118 } 119 if err := ec.st.db().Run(buildTxn); err != nil { 120 return nil, errors.Annotate(err, "failed to save external controller") 121 } 122 123 return &externalController{ 124 doc: *doc, 125 }, nil 126 } 127 128 // SaveAndMoveModels is the same as `Save`, but if any of the input model UUIDs 129 // are in other external external controllers, those records will be updated 130 // to disassociate them. 131 func (ec *externalControllers) SaveAndMoveModels(controller crossmodel.ControllerInfo, modelUUIDs ...string) error { 132 if err := controller.Validate(); err != nil { 133 return errors.Trace(err) 134 } 135 doc := newExternalControllerDoc(controller) 136 buildTxn := func(int) ([]txn.Op, error) { 137 // Find any controllers that already have one of 138 // the input model UUIDs associated with it. 139 controllers, err := ec.st.externalControllerDocsForModels(modelUUIDs...) 140 if err != nil { 141 return nil, errors.Trace(err) 142 } 143 144 var ops []txn.Op 145 changingModels := set.NewStrings(modelUUIDs...) 146 for _, controller := range controllers { 147 // If the controller to be saved already has one 148 // of the input model UUIDs, do not change it. 149 if controller.Id == doc.Id { 150 continue 151 } 152 153 // If the models for the controller need changing, 154 // generate a transaction operation. 155 currentModels := set.NewStrings(controller.Models...) 156 modelDiff := currentModels.Difference(changingModels) 157 if modelDiff.Size() != currentModels.Size() { 158 // TODO (manadart 2019-12-13): There is a case for deleting 159 // records with no more associated model UUIDs. 160 // We are being conservative here and keeping them. 161 ops = append(ops, txn.Op{ 162 C: externalControllersC, 163 Id: controller.Id, 164 Assert: txn.DocExists, 165 Update: bson.D{{"$set", bson.D{{"models", modelDiff.SortedValues()}}}}, 166 }) 167 } 168 } 169 170 newControllerOps, err := ec.upsertExternalControllerOps(doc, modelUUIDs) 171 if err != nil { 172 return nil, errors.Trace(err) 173 } 174 return append(ops, newControllerOps...), nil 175 } 176 return errors.Annotate(ec.st.db().Run(buildTxn), "saving location of external models") 177 } 178 179 // upsertExternalControllerOps returns the transaction operations for saving 180 // the input controller document as the location of the input models. 181 func (ec *externalControllers) upsertExternalControllerOps( 182 doc *externalControllerDoc, modelUUIDs []string, 183 ) ([]txn.Op, error) { 184 model, err := ec.st.Model() 185 if err != nil { 186 return nil, errors.Annotate(err, "failed to load model") 187 } 188 if err := checkModelActive(ec.st); err != nil { 189 return nil, errors.Trace(err) 190 } 191 existing, err := ec.controller(doc.Id) 192 if err != nil && !errors.IsNotFound(err) { 193 return nil, errors.Trace(err) 194 } 195 upsertOp := upsertExternalControllerOp(doc, existing, modelUUIDs) 196 ops := []txn.Op{ 197 upsertOp, 198 model.assertActiveOp(), 199 } 200 return ops, nil 201 } 202 203 // Remove removes an external controller record with the given controller UUID. 204 func (ec *externalControllers) Remove(controllerUUID string) error { 205 ops := []txn.Op{{ 206 C: externalControllersC, 207 Id: controllerUUID, 208 Remove: true, 209 }} 210 err := ec.st.db().RunTransaction(ops) 211 return errors.Annotate(err, "failed to remove external controller") 212 } 213 214 // Controller retrieves an ExternalController with a given controller UUID. 215 func (ec *externalControllers) Controller(controllerUUID string) (ExternalController, error) { 216 doc, err := ec.controller(controllerUUID) 217 if err != nil { 218 return nil, errors.Trace(err) 219 } 220 return &externalController{*doc}, nil 221 } 222 223 func (ec *externalControllers) controller(controllerUUID string) (*externalControllerDoc, error) { 224 coll, closer := ec.st.db().GetCollection(externalControllersC) 225 defer closer() 226 227 var doc externalControllerDoc 228 err := coll.FindId(controllerUUID).One(&doc) 229 if err == mgo.ErrNotFound { 230 return nil, errors.NotFoundf("external controller with UUID %v", controllerUUID) 231 } 232 if err != nil { 233 return nil, errors.Trace(err) 234 } 235 return &doc, nil 236 } 237 238 // ControllerForModel retrieves an ExternalController with a given model UUID. 239 func (ec *externalControllers) ControllerForModel(modelUUID string) (ExternalController, error) { 240 return ec.st.ExternalControllerForModel(modelUUID) 241 } 242 243 // Watch returns a strings watcher that watches for addition and removal of 244 // external controller documents. The strings returned will be the controller 245 // UUIDs. 246 func (ec *externalControllers) Watch() StringsWatcher { 247 return newExternalControllersWatcher(ec.st) 248 } 249 250 // WatchController returns a notify watcher that watches for changes to the 251 // external controller with the specified controller UUID. 252 func (ec *externalControllers) WatchController(controllerUUID string) NotifyWatcher { 253 return newEntityWatcher(ec.st, externalControllersC, controllerUUID) 254 } 255 256 // ExternalControllerForModel retrieves an ExternalController with a given 257 // model UUID. 258 // This is very similar to externalControllers.ControllerForModel, except the 259 // return type is a lot less strict, one that we can access the ModelUUIDs from 260 // the controller. 261 func (st *State) ExternalControllerForModel(modelUUID string) (*externalController, error) { 262 docs, err := st.externalControllerDocsForModels(modelUUID) 263 if err != nil { 264 return nil, errors.Trace(err) 265 } 266 switch len(docs) { 267 case 0: 268 return nil, errors.NotFoundf("external controller with model %v", modelUUID) 269 case 1: 270 return &externalController{doc: docs[0]}, nil 271 } 272 return nil, errors.Errorf("expected 1 controller with model %v, got %d", modelUUID, len(docs)) 273 } 274 275 func (st *State) externalControllerDocsForModels(modelUUIDs ...string) ([]externalControllerDoc, error) { 276 coll, closer := st.db().GetCollection(externalControllersC) 277 defer closer() 278 279 var docs []externalControllerDoc 280 err := coll.Find(bson.M{"models": bson.M{"$in": modelUUIDs}}).All(&docs) 281 return docs, errors.Trace(err) 282 } 283 284 func upsertExternalControllerOp(doc, existing *externalControllerDoc, modelUUIDs []string) txn.Op { 285 if existing != nil { 286 models := set.NewStrings(existing.Models...) 287 models = models.Union(set.NewStrings(modelUUIDs...)) 288 return txn.Op{ 289 C: externalControllersC, 290 Id: existing.Id, 291 Assert: txn.DocExists, 292 Update: bson.D{ 293 {"$set", 294 bson.D{ 295 {"addresses", doc.Addrs}, 296 {"alias", doc.Alias}, 297 {"cacert", doc.CACert}, 298 {"models", models.SortedValues()}, 299 }, 300 }, 301 }, 302 } 303 } 304 305 doc.Models = modelUUIDs 306 return txn.Op{ 307 C: externalControllersC, 308 Id: doc.Id, 309 Assert: txn.DocMissing, 310 Insert: *doc, 311 } 312 } 313 314 // externalControllerRefCountKey returns a key for refcounting consumed 315 // apps for the specified controller. Each time a consumed app is created, 316 // the refcount is incremented, and the opposite happens on removal. 317 func externalControllerRefCountKey(controllerUUID string) string { 318 return fmt.Sprintf("controller#%s", controllerUUID) 319 } 320 321 // incExternalControllersRefOp returns a txn.Op that increments the reference 322 // count for an external controller. These ref counts are controller wide. 323 func incExternalControllersRefOp(mb modelBackend, controllerUUID string) (txn.Op, error) { 324 refcounts, closer := mb.db().GetCollection(globalRefcountsC) 325 defer closer() 326 refCountKey := externalControllerRefCountKey(controllerUUID) 327 incRefOp, err := nsRefcounts.CreateOrIncRefOp(refcounts, refCountKey, 1) 328 return incRefOp, errors.Trace(err) 329 } 330 331 // decExternalControllersRefOp returns a txn.Op that decrements the reference 332 // count for an external controller. These ref counts are controller wide. 333 func decExternalControllersRefOp(mb modelBackend, controllerUUID string) (txn.Op, bool, error) { 334 refcounts, closer := mb.db().GetCollection(globalRefcountsC) 335 defer closer() 336 refCountKey := externalControllerRefCountKey(controllerUUID) 337 decRefOp, isFinal, err := nsRefcounts.DyingDecRefOp(refcounts, refCountKey) 338 if err != nil { 339 return txn.Op{}, false, errors.Trace(err) 340 } 341 return decRefOp, isFinal, nil 342 }