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

     1  // Copyright 2020 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"github.com/juju/mgo/v3/bson"
     8  	"github.com/juju/mgo/v3/txn"
     9  	jujutxn "github.com/juju/txn/v3"
    10  
    11  	"github.com/juju/juju/core/leadership"
    12  	mgoutils "github.com/juju/juju/mongo/utils"
    13  )
    14  
    15  type updateLeaderSettingsOperation struct {
    16  	db Database
    17  
    18  	sets   bson.M
    19  	unsets bson.M
    20  
    21  	key       string
    22  	updateDoc bson.D
    23  
    24  	tokenAwareTxnBuilder func(int) ([]txn.Op, error)
    25  }
    26  
    27  // newApplicationUpdateLeaderSettingsOperation returns a ModelOperation for
    28  // updating the leader settings for a particular application.
    29  func newUpdateLeaderSettingsOperation(db Database, token leadership.Token, key string, updates map[string]interface{}) ModelOperation {
    30  	// We can calculate the actual update ahead of time; it's not dependent
    31  	// upon the current state of the document. (*Writing* it should depend
    32  	// on document state, but that's handled below.)
    33  	sets := bson.M{}
    34  	unsets := bson.M{}
    35  	for unescapedKey, value := range updates {
    36  		key := mgoutils.EscapeKey(unescapedKey)
    37  		if value == "" {
    38  			unsets[key] = 1
    39  		} else {
    40  			sets[key] = value
    41  		}
    42  	}
    43  	updateDoc := setUnsetUpdateSettings(sets, unsets)
    44  
    45  	op := &updateLeaderSettingsOperation{
    46  		db:        db,
    47  		sets:      sets,
    48  		unsets:    unsets,
    49  		key:       key,
    50  		updateDoc: updateDoc,
    51  	}
    52  
    53  	op.tokenAwareTxnBuilder = buildTxnWithLeadership(op.buildTxn, token)
    54  	return op
    55  }
    56  
    57  // Build implements ModelOperation.
    58  func (op *updateLeaderSettingsOperation) Build(attempt int) ([]txn.Op, error) {
    59  	return op.tokenAwareTxnBuilder(attempt)
    60  }
    61  
    62  func (op *updateLeaderSettingsOperation) buildTxn(_ int) ([]txn.Op, error) {
    63  	// Read the current document state so we can abort if there's
    64  	// no actual change; and the version number so we can assert
    65  	// on it and prevent these settings from landing late.
    66  	doc, err := readSettingsDoc(op.db, settingsC, op.key)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	if op.isNullChange(doc.Settings) {
    71  		return nil, jujutxn.ErrNoOperations
    72  	}
    73  	return []txn.Op{{
    74  		C:      settingsC,
    75  		Id:     op.key,
    76  		Assert: bson.D{{"version", doc.Version}},
    77  		Update: op.updateDoc,
    78  	}}, nil
    79  }
    80  
    81  func (op *updateLeaderSettingsOperation) isNullChange(rawMap map[string]interface{}) bool {
    82  	for key := range op.unsets {
    83  		if _, found := rawMap[key]; found {
    84  			return false
    85  		}
    86  	}
    87  	for key, value := range op.sets {
    88  		if current := rawMap[key]; current != value {
    89  			return false
    90  		}
    91  	}
    92  	return true
    93  }
    94  
    95  // Done implements ModelOperation.
    96  func (op *updateLeaderSettingsOperation) Done(err error) error { return err }