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 }