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  }