github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	"gopkg.in/juju/names.v2"
    11  	"gopkg.in/mgo.v2"
    12  	"gopkg.in/mgo.v2/bson"
    13  	"gopkg.in/mgo.v2/txn"
    14  
    15  	"github.com/juju/juju/state/multiwatcher"
    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, services, 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]multiwatcher.BlockType{
    57  		DestroyBlock: multiwatcher.BlockDestroy,
    58  		RemoveBlock:  multiwatcher.BlockRemove,
    59  		ChangeBlock:  multiwatcher.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 multiwatcher.BlockType.
    78  func (t BlockType) ToParams() multiwatcher.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  //     not found -> nil, false, nil
   175  //     found -> block, true, nil
   176  //     error -> nil, false, err
   177  func (st *State) GetBlockForType(t BlockType) (Block, bool, error) {
   178  	all, closer := st.getCollection(blocksC)
   179  	defer closer()
   180  
   181  	doc := blockDoc{}
   182  	err := all.Find(bson.D{{"type", t}}).One(&doc)
   183  
   184  	switch err {
   185  	case nil:
   186  		return &block{doc}, true, nil
   187  	case mgo.ErrNotFound:
   188  		return nil, false, nil
   189  	default:
   190  		return nil, false, errors.Annotatef(err, "cannot get block of type %v", t.String())
   191  	}
   192  }
   193  
   194  // AllBlocks returns all blocks in the model.
   195  func (st *State) AllBlocks() ([]Block, error) {
   196  	blocksCollection, closer := st.getCollection(blocksC)
   197  	defer closer()
   198  
   199  	var bdocs []blockDoc
   200  	err := blocksCollection.Find(nil).All(&bdocs)
   201  	if err != nil {
   202  		return nil, errors.Annotatef(err, "cannot get all blocks")
   203  	}
   204  	blocks := make([]Block, len(bdocs))
   205  	for i, doc := range bdocs {
   206  		blocks[i] = &block{doc}
   207  	}
   208  	return blocks, nil
   209  }
   210  
   211  // AllBlocksForController returns all blocks in any models on
   212  // the controller.
   213  func (st *State) AllBlocksForController() ([]Block, error) {
   214  	blocksCollection, closer := st.getRawCollection(blocksC)
   215  	defer closer()
   216  
   217  	var bdocs []blockDoc
   218  	err := blocksCollection.Find(nil).All(&bdocs)
   219  	if err != nil {
   220  		return nil, errors.Annotate(err, "cannot get all blocks")
   221  	}
   222  	blocks := make([]Block, len(bdocs))
   223  	for i, doc := range bdocs {
   224  		blocks[i] = &block{doc}
   225  	}
   226  
   227  	return blocks, nil
   228  }
   229  
   230  // RemoveAllBlocksForController removes all the blocks for the controller.
   231  // It does not prevent new blocks from being added during / after
   232  // removal.
   233  func (st *State) RemoveAllBlocksForController() error {
   234  	blocks, err := st.AllBlocksForController()
   235  	if err != nil {
   236  		return errors.Trace(err)
   237  	}
   238  
   239  	ops := []txn.Op{}
   240  	for _, blk := range blocks {
   241  		ops = append(ops, txn.Op{
   242  			C:      blocksC,
   243  			Id:     blk.Id(),
   244  			Remove: true,
   245  		})
   246  	}
   247  
   248  	// Use runRawTransaction as we might be removing docs across
   249  	// multiple models.
   250  	return st.runRawTransaction(ops)
   251  }
   252  
   253  // setModelBlock updates the blocks collection with the
   254  // specified block.
   255  // Only one instance of each block type can exist in model.
   256  func setModelBlock(st *State, t BlockType, msg string) error {
   257  	buildTxn := func(attempt int) ([]txn.Op, error) {
   258  		block, exists, err := st.GetBlockForType(t)
   259  		if err != nil {
   260  			return nil, errors.Trace(err)
   261  		}
   262  		// Cannot create blocks of the same type more than once per model.
   263  		// Cannot update current blocks.
   264  		if exists {
   265  			return block.updateMessageOp(msg)
   266  		}
   267  		return createModelBlockOps(st, t, msg)
   268  	}
   269  	return st.run(buildTxn)
   270  }
   271  
   272  // newBlockId returns a sequential block id for this model.
   273  func newBlockId(st *State) (string, error) {
   274  	seq, err := st.sequence("block")
   275  	if err != nil {
   276  		return "", errors.Trace(err)
   277  	}
   278  	return fmt.Sprint(seq), nil
   279  }
   280  
   281  func createModelBlockOps(st *State, t BlockType, msg string) ([]txn.Op, error) {
   282  	id, err := newBlockId(st)
   283  	if err != nil {
   284  		return nil, errors.Annotatef(err, "getting new block id")
   285  	}
   286  	// NOTE: if at any time in the future, we change blocks so that the
   287  	// Tag is different from the model, then the migration of blocks will
   288  	// need to change format.
   289  	newDoc := blockDoc{
   290  		DocID:     st.docID(id),
   291  		ModelUUID: st.ModelUUID(),
   292  		Tag:       st.ModelTag().String(),
   293  		Type:      t,
   294  		Message:   msg,
   295  	}
   296  	insertOp := txn.Op{
   297  		C:      blocksC,
   298  		Id:     newDoc.DocID,
   299  		Assert: txn.DocMissing,
   300  		Insert: &newDoc,
   301  	}
   302  	return []txn.Op{insertOp}, nil
   303  }
   304  
   305  func RemoveModelBlock(st *State, t BlockType) error {
   306  	buildTxn := func(attempt int) ([]txn.Op, error) {
   307  		return RemoveModelBlockOps(st, t)
   308  	}
   309  	return st.run(buildTxn)
   310  }
   311  
   312  func RemoveModelBlockOps(st *State, t BlockType) ([]txn.Op, error) {
   313  	tBlock, exists, err := st.GetBlockForType(t)
   314  	if err != nil {
   315  		return nil, errors.Annotatef(err, "removing block %v", t.String())
   316  	}
   317  	if exists {
   318  		return []txn.Op{txn.Op{
   319  			C:      blocksC,
   320  			Id:     tBlock.Id(),
   321  			Remove: true,
   322  		}}, nil
   323  	}
   324  	// If the block doesn't exist, we're all good.
   325  	return nil, nil
   326  }