github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/charmref.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/mgo/v3/txn"
     9  )
    10  
    11  var errCharmInUse = errors.New("charm in use")
    12  
    13  // appCharmIncRefOps returns the operations necessary to record a reference
    14  // to a charm and its per-application settings and storage constraints
    15  // documents. It will fail if the charm is not Alive.
    16  func appCharmIncRefOps(mb modelBackend, appName string, cURL *string, canCreate bool) ([]txn.Op, error) {
    17  	charms, cCloser := mb.db().GetCollection(charmsC)
    18  	defer cCloser()
    19  
    20  	// If we're migrating. charm document will not be present. But
    21  	// if we're not migrating, we need to check the charm is alive.
    22  	var checkOps []txn.Op
    23  	count, err := charms.FindId(*cURL).Count()
    24  	if err != nil {
    25  		return nil, errors.Annotate(err, "charm")
    26  	} else if count != 0 {
    27  		checkOp, err := nsLife.aliveOp(charms, *cURL)
    28  		if err != nil {
    29  			return nil, errors.Annotate(err, "charm")
    30  		}
    31  		checkOps = []txn.Op{checkOp}
    32  	}
    33  
    34  	refcounts, rCloser := mb.db().GetCollection(refcountsC)
    35  	defer rCloser()
    36  
    37  	getIncRefOp := nsRefcounts.CreateOrIncRefOp
    38  	if !canCreate {
    39  		getIncRefOp = nsRefcounts.StrictIncRefOp
    40  	}
    41  	settingsKey := applicationCharmConfigKey(appName, cURL)
    42  	settingsOp, err := getIncRefOp(refcounts, settingsKey, 1)
    43  	if err != nil {
    44  		return nil, errors.Annotate(err, "settings reference")
    45  	}
    46  	storageConstraintsKey := applicationStorageConstraintsKey(appName, cURL)
    47  	storageConstraintsOp, err := getIncRefOp(refcounts, storageConstraintsKey, 1)
    48  	if err != nil {
    49  		return nil, errors.Annotate(err, "storage constraints reference")
    50  	}
    51  	charmKey := charmGlobalKey(cURL)
    52  	charmOp, err := getIncRefOp(refcounts, charmKey, 1)
    53  	if err != nil {
    54  		return nil, errors.Annotate(err, "charm reference")
    55  	}
    56  
    57  	return append(checkOps, settingsOp, storageConstraintsOp, charmOp), nil
    58  }
    59  
    60  // appCharmDecRefOps returns the operations necessary to delete a
    61  // reference to a charm and its per-application settings and storage
    62  // constraints document.
    63  // If maybeDoFinal is true, and no references to a given (app, charm) pair
    64  // remain, the operations returned will also remove the settings and
    65  // storage constraints documents for that pair, and schedule a cleanup
    66  // to see if the charm itself is now unreferenced and can be tidied
    67  // away itself.
    68  // When 'force' is set, this call will return some, if not all, needed operations
    69  // and will accumulate operational errors encountered in the operation.
    70  // If the 'force' is not set, any error will be fatal and no operations will be returned.
    71  func appCharmDecRefOps(st modelBackend, appName string, cURL *string, maybeDoFinal bool, op *ForcedOperation) ([]txn.Op, error) {
    72  	refcounts, closer := st.db().GetCollection(refcountsC)
    73  	defer closer()
    74  
    75  	fail := func(e error) ([]txn.Op, error) {
    76  		return nil, errors.Trace(e)
    77  	}
    78  	ops := []txn.Op{}
    79  	charmKey := charmGlobalKey(cURL)
    80  	charmOp, err := nsRefcounts.AliveDecRefOp(refcounts, charmKey)
    81  	if op.FatalError(err) {
    82  		return fail(errors.Annotate(err, "charm reference"))
    83  	}
    84  	if err == nil {
    85  		ops = append(ops, charmOp)
    86  	}
    87  
    88  	settingsKey := applicationCharmConfigKey(appName, cURL)
    89  	settingsOp, isFinal, err := nsRefcounts.DyingDecRefOp(refcounts, settingsKey)
    90  	if op.FatalError(err) {
    91  		return fail(errors.Annotatef(err, "settings reference %s", settingsKey))
    92  	}
    93  	if err == nil {
    94  		ops = append(ops, settingsOp)
    95  	}
    96  
    97  	storageConstraintsKey := applicationStorageConstraintsKey(appName, cURL)
    98  	storageConstraintsOp, _, err := nsRefcounts.DyingDecRefOp(refcounts, storageConstraintsKey)
    99  	if op.FatalError(err) {
   100  		return fail(errors.Annotatef(err, "storage constraints reference %s", storageConstraintsKey))
   101  	}
   102  	if err == nil {
   103  		ops = append(ops, storageConstraintsOp)
   104  	}
   105  
   106  	if isFinal && maybeDoFinal {
   107  		// XXX(fwereade): this construction, in common with ~all
   108  		// our refcount logic, is safe in parallel but not in
   109  		// serial. If this logic is used twice while composing a
   110  		// single transaction, the removal won't be triggered.
   111  		// see `Application.removeOps` for the workaround.
   112  		ops = append(ops, finalAppCharmRemoveOps(appName, cURL)...)
   113  	}
   114  	return ops, nil
   115  }
   116  
   117  // finalAppCharmRemoveOps returns operations to delete the settings
   118  // and storage, device constraints documents and queue a charm cleanup.
   119  func finalAppCharmRemoveOps(appName string, curl *string) []txn.Op {
   120  	settingsKey := applicationCharmConfigKey(appName, curl)
   121  	removeSettingsOp := txn.Op{
   122  		C:      settingsC,
   123  		Id:     settingsKey,
   124  		Remove: true,
   125  	}
   126  	// ensure removing storage constraints doc
   127  	storageConstraintsKey := applicationStorageConstraintsKey(appName, curl)
   128  	removeStorageConstraintsOp := removeStorageConstraintsOp(storageConstraintsKey)
   129  	// ensure removing device constraints doc
   130  	deviceConstraintsKey := applicationDeviceConstraintsKey(appName, curl)
   131  	removeDeviceConstraintsOp := removeDeviceConstraintsOp(deviceConstraintsKey)
   132  
   133  	cleanupOp := newCleanupOp(cleanupCharm, *curl)
   134  	return []txn.Op{removeSettingsOp, removeStorageConstraintsOp, removeDeviceConstraintsOp, cleanupOp}
   135  }
   136  
   137  // charmDestroyOps implements the logic of charm.Destroy.
   138  func charmDestroyOps(st modelBackend, curl string) ([]txn.Op, error) {
   139  	if curl == "" {
   140  		return nil, errors.BadRequestf("curl is empty")
   141  	}
   142  	db := st.db()
   143  	charms, cCloser := db.GetCollection(charmsC)
   144  	defer cCloser()
   145  
   146  	charmOp, err := nsLife.destroyOp(charms, curl, nil)
   147  	if err != nil {
   148  		return nil, errors.Annotate(err, "charm")
   149  	}
   150  
   151  	refcounts, rCloser := db.GetCollection(refcountsC)
   152  	defer rCloser()
   153  
   154  	refcountKey := charmGlobalKey(&curl)
   155  	refcountOp, err := nsRefcounts.RemoveOp(refcounts, refcountKey, 0)
   156  	switch errors.Cause(err) {
   157  	case nil:
   158  	case errRefcountChanged:
   159  		return nil, errCharmInUse
   160  	default:
   161  		return nil, errors.Annotate(err, "charm reference")
   162  	}
   163  
   164  	return []txn.Op{charmOp, refcountOp}, nil
   165  }