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 }