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

     1  // Copyright 2015 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/errors"
    10  	"github.com/juju/mgo/v3"
    11  	"github.com/juju/mgo/v3/bson"
    12  	"github.com/juju/mgo/v3/txn"
    13  	"github.com/juju/names/v5"
    14  
    15  	"github.com/juju/juju/core/model"
    16  )
    17  
    18  // Customers and stakeholders want to be able to prevent accidental damage to their Juju deployments.
    19  // To prevent running some operations, we want to have blocks that can be switched on/off.
    20  type Block interface {
    21  	// Id returns this block's id.
    22  	Id() string
    23  
    24  	// ModelUUID returns the model UUID associated with this block.
    25  	ModelUUID() string
    26  
    27  	// Tag returns tag for the entity that is being blocked
    28  	Tag() (names.Tag, error)
    29  
    30  	// Type returns block type
    31  	Type() BlockType
    32  
    33  	// Message returns explanation that accompanies this block.
    34  	Message() string
    35  
    36  	updateMessageOp(string) ([]txn.Op, error)
    37  }
    38  
    39  // BlockType specifies block type for enum benefit.
    40  type BlockType int8
    41  
    42  const (
    43  	// DestroyBlock type identifies block that prevents model destruction.
    44  	DestroyBlock BlockType = iota
    45  
    46  	// RemoveBlock type identifies block that prevents
    47  	// removal of machines, applications, units or relations.
    48  	RemoveBlock
    49  
    50  	// ChangeBlock type identifies block that prevents model changes such
    51  	// as additions, modifications, removals of model entities.
    52  	ChangeBlock
    53  )
    54  
    55  var (
    56  	typeNames = map[BlockType]model.BlockType{
    57  		DestroyBlock: model.BlockDestroy,
    58  		RemoveBlock:  model.BlockRemove,
    59  		ChangeBlock:  model.BlockChange,
    60  	}
    61  	blockMigrationValue = map[BlockType]string{
    62  		DestroyBlock: "destroy-model",
    63  		RemoveBlock:  "remove-object",
    64  		ChangeBlock:  "all-changes",
    65  	}
    66  )
    67  
    68  // AllTypes returns all supported block types.
    69  func AllTypes() []BlockType {
    70  	return []BlockType{
    71  		DestroyBlock,
    72  		RemoveBlock,
    73  		ChangeBlock,
    74  	}
    75  }
    76  
    77  // ToParams returns the type as model.BlockType.
    78  func (t BlockType) ToParams() model.BlockType {
    79  	if jujuBlock, ok := typeNames[t]; ok {
    80  		return jujuBlock
    81  	}
    82  	panic(fmt.Sprintf("unknown block type %d", int(t)))
    83  }
    84  
    85  // String returns humanly readable type representation.
    86  func (t BlockType) String() string {
    87  	return string(t.ToParams())
    88  }
    89  
    90  // MigrationValue converts the block type value into a useful human readable
    91  // string for model migration.
    92  func (t BlockType) MigrationValue() string {
    93  	if value, ok := blockMigrationValue[t]; ok {
    94  		return value
    95  	}
    96  	return "unknown"
    97  }
    98  
    99  // ParseBlockType returns BlockType from humanly readable type representation.
   100  func ParseBlockType(str string) BlockType {
   101  	for _, one := range AllTypes() {
   102  		if one.String() == str {
   103  			return one
   104  		}
   105  	}
   106  	panic(fmt.Sprintf("unknown block type %v", str))
   107  }
   108  
   109  type block struct {
   110  	doc blockDoc
   111  }
   112  
   113  // blockDoc records information about an model block.
   114  type blockDoc struct {
   115  	DocID     string    `bson:"_id"`
   116  	ModelUUID string    `bson:"model-uuid"`
   117  	Tag       string    `bson:"tag"`
   118  	Type      BlockType `bson:"type"`
   119  	Message   string    `bson:"message,omitempty"`
   120  }
   121  
   122  func (b *block) updateMessageOp(message string) ([]txn.Op, error) {
   123  	return []txn.Op{{
   124  		C:      blocksC,
   125  		Id:     b.doc.DocID,
   126  		Assert: txn.DocExists,
   127  		Update: bson.D{{"$set", bson.D{{"message", message}}}},
   128  	}}, nil
   129  }
   130  
   131  // Id is part of the state.Block interface.
   132  func (b *block) Id() string {
   133  	return b.doc.DocID
   134  }
   135  
   136  // ModelUUID is part of the state.Block interface.
   137  func (b *block) ModelUUID() string {
   138  	return b.doc.ModelUUID
   139  }
   140  
   141  // Message is part of the state.Block interface.
   142  func (b *block) Message() string {
   143  	return b.doc.Message
   144  }
   145  
   146  // Tag is part of the state.Block interface.
   147  func (b *block) Tag() (names.Tag, error) {
   148  	tag, err := names.ParseTag(b.doc.Tag)
   149  	if err != nil {
   150  		return nil, errors.Annotatef(err, "getting block information")
   151  	}
   152  	return tag, nil
   153  }
   154  
   155  // Type is part of the state.Block interface.
   156  func (b *block) Type() BlockType {
   157  	return b.doc.Type
   158  }
   159  
   160  // SwitchBlockOn enables block of specified type for the
   161  // current model.
   162  func (st *State) SwitchBlockOn(t BlockType, msg string) error {
   163  	return setModelBlock(st, t, msg)
   164  }
   165  
   166  // SwitchBlockOff disables block of specified type for the
   167  // current model.
   168  func (st *State) SwitchBlockOff(t BlockType) error {
   169  	return RemoveModelBlock(st, t)
   170  }
   171  
   172  // GetBlockForType returns the Block of the specified type for the current model
   173  // where
   174  //
   175  //	not found -> nil, false, nil
   176  //	found -> block, true, nil
   177  //	error -> nil, false, err
   178  func (st *State) GetBlockForType(t BlockType) (Block, bool, error) {
   179  	return getBlockForType(st, t)
   180  }
   181  
   182  func getBlockForType(mb modelBackend, t BlockType) (Block, bool, error) {
   183  	all, closer := mb.db().GetCollection(blocksC)
   184  	defer closer()
   185  
   186  	doc := blockDoc{}
   187  	err := all.Find(bson.D{{"type", t}}).One(&doc)
   188  
   189  	switch err {
   190  	case nil:
   191  		return &block{doc}, true, nil
   192  	case mgo.ErrNotFound:
   193  		return nil, false, nil
   194  	default:
   195  		return nil, false, errors.Annotatef(err, "cannot get block of type %v", t.String())
   196  	}
   197  }
   198  
   199  // AllBlocks returns all blocks in the model.
   200  func (st *State) AllBlocks() ([]Block, error) {
   201  	blocksCollection, closer := st.db().GetCollection(blocksC)
   202  	defer closer()
   203  
   204  	var bdocs []blockDoc
   205  	err := blocksCollection.Find(nil).All(&bdocs)
   206  	if err != nil {
   207  		return nil, errors.Annotatef(err, "cannot get all blocks")
   208  	}
   209  	blocks := make([]Block, len(bdocs))
   210  	for i, doc := range bdocs {
   211  		blocks[i] = &block{doc}
   212  	}
   213  	return blocks, nil
   214  }
   215  
   216  // AllBlocksForController returns all blocks in any models on
   217  // the controller.
   218  func (st *State) AllBlocksForController() ([]Block, error) {
   219  	blocksCollection, closer := st.db().GetRawCollection(blocksC)
   220  	defer closer()
   221  
   222  	var bdocs []blockDoc
   223  	err := blocksCollection.Find(nil).All(&bdocs)
   224  	if err != nil {
   225  		return nil, errors.Annotate(err, "cannot get all blocks")
   226  	}
   227  	blocks := make([]Block, len(bdocs))
   228  	for i, doc := range bdocs {
   229  		blocks[i] = &block{doc}
   230  	}
   231  
   232  	return blocks, nil
   233  }
   234  
   235  // RemoveAllBlocksForController removes all the blocks for the controller.
   236  // It does not prevent new blocks from being added during / after
   237  // removal.
   238  func (st *State) RemoveAllBlocksForController() error {
   239  	blocks, err := st.AllBlocksForController()
   240  	if err != nil {
   241  		return errors.Trace(err)
   242  	}
   243  
   244  	ops := []txn.Op{}
   245  	for _, blk := range blocks {
   246  		ops = append(ops, txn.Op{
   247  			C:      blocksC,
   248  			Id:     blk.Id(),
   249  			Remove: true,
   250  		})
   251  	}
   252  
   253  	// Use runRawTransaction as we might be removing docs across
   254  	// multiple models.
   255  	return st.runRawTransaction(ops)
   256  }
   257  
   258  // setModelBlock updates the blocks collection with the
   259  // specified block.
   260  // Only one instance of each block type can exist in model.
   261  func setModelBlock(mb modelBackend, t BlockType, msg string) error {
   262  	buildTxn := func(attempt int) ([]txn.Op, error) {
   263  		block, exists, err := getBlockForType(mb, t)
   264  		if err != nil {
   265  			return nil, errors.Trace(err)
   266  		}
   267  		// Cannot create blocks of the same type more than once per model.
   268  		// Cannot update current blocks.
   269  		if exists {
   270  			return block.updateMessageOp(msg)
   271  		}
   272  		return createModelBlockOps(mb, t, msg)
   273  	}
   274  	return mb.db().Run(buildTxn)
   275  }
   276  
   277  // newBlockId returns a sequential block id for this model.
   278  func newBlockId(mb modelBackend) (string, error) {
   279  	seq, err := sequence(mb, "block")
   280  	if err != nil {
   281  		return "", errors.Trace(err)
   282  	}
   283  	return fmt.Sprint(seq), nil
   284  }
   285  
   286  func createModelBlockOps(mb modelBackend, t BlockType, msg string) ([]txn.Op, error) {
   287  	id, err := newBlockId(mb)
   288  	if err != nil {
   289  		return nil, errors.Annotatef(err, "getting new block id")
   290  	}
   291  	// NOTE: if at any time in the future, we change blocks so that the
   292  	// Tag is different from the model, then the migration of blocks will
   293  	// need to change format.
   294  	newDoc := blockDoc{
   295  		DocID:     mb.docID(id),
   296  		ModelUUID: mb.ModelUUID(),
   297  		Tag:       names.NewModelTag(mb.ModelUUID()).String(),
   298  		Type:      t,
   299  		Message:   msg,
   300  	}
   301  	insertOp := txn.Op{
   302  		C:      blocksC,
   303  		Id:     newDoc.DocID,
   304  		Assert: txn.DocMissing,
   305  		Insert: &newDoc,
   306  	}
   307  	return []txn.Op{insertOp}, nil
   308  }
   309  
   310  func RemoveModelBlock(st *State, t BlockType) error {
   311  	buildTxn := func(attempt int) ([]txn.Op, error) {
   312  		return RemoveModelBlockOps(st, t)
   313  	}
   314  	return st.db().Run(buildTxn)
   315  }
   316  
   317  func RemoveModelBlockOps(st *State, t BlockType) ([]txn.Op, error) {
   318  	tBlock, exists, err := st.GetBlockForType(t)
   319  	if err != nil {
   320  		return nil, errors.Annotatef(err, "removing block %v", t.String())
   321  	}
   322  	if exists {
   323  		return []txn.Op{{
   324  			C:      blocksC,
   325  			Id:     tBlock.Id(),
   326  			Remove: true,
   327  		}}, nil
   328  	}
   329  	// If the block doesn't exist, we're all good.
   330  	return nil, nil
   331  }