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 }