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