github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/state_database.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "fmt" 6 7 "github.com/boltdb/bolt" 8 "github.com/hashicorp/nomad/nomad/structs" 9 "github.com/ugorji/go/codec" 10 ) 11 12 /* 13 The client has a boltDB backed state store. The schema as of 0.6 looks as follows: 14 15 allocations/ (bucket) 16 |--> <alloc-id>/ (bucket) 17 |--> alloc_runner persisted objects (k/v) 18 |--> <task-name>/ (bucket) 19 |--> task_runner persisted objects (k/v) 20 */ 21 22 var ( 23 // allocationsBucket is the bucket name containing all allocation related 24 // data 25 allocationsBucket = []byte("allocations") 26 ) 27 28 func putObject(bkt *bolt.Bucket, key []byte, obj interface{}) error { 29 if !bkt.Writable() { 30 return fmt.Errorf("bucket must be writable") 31 } 32 33 // Serialize the object 34 var buf bytes.Buffer 35 if err := codec.NewEncoder(&buf, structs.MsgpackHandle).Encode(obj); err != nil { 36 return fmt.Errorf("failed to encode passed object: %v", err) 37 } 38 39 if err := bkt.Put(key, buf.Bytes()); err != nil { 40 return fmt.Errorf("failed to write data at key %v: %v", string(key), err) 41 } 42 43 return nil 44 } 45 46 func putData(bkt *bolt.Bucket, key, value []byte) error { 47 if !bkt.Writable() { 48 return fmt.Errorf("bucket must be writable") 49 } 50 51 if err := bkt.Put(key, value); err != nil { 52 return fmt.Errorf("failed to write data at key %v: %v", string(key), err) 53 } 54 55 return nil 56 } 57 58 func getObject(bkt *bolt.Bucket, key []byte, obj interface{}) error { 59 // Get the data 60 data := bkt.Get(key) 61 if data == nil { 62 return fmt.Errorf("no data at key %v", string(key)) 63 } 64 65 // Deserialize the object 66 if err := codec.NewDecoderBytes(data, structs.MsgpackHandle).Decode(obj); err != nil { 67 return fmt.Errorf("failed to decode data into passed object: %v", err) 68 } 69 70 return nil 71 } 72 73 // getAllocationBucket returns the bucket used to persist state about a 74 // particular allocation. If the root allocation bucket or the specific 75 // allocation bucket doesn't exist, it will be created as long as the 76 // transaction is writable. 77 func getAllocationBucket(tx *bolt.Tx, allocID string) (*bolt.Bucket, error) { 78 var err error 79 w := tx.Writable() 80 81 // Retrieve the root allocations bucket 82 allocations := tx.Bucket(allocationsBucket) 83 if allocations == nil { 84 if !w { 85 return nil, fmt.Errorf("Allocations bucket doesn't exist and transaction is not writable") 86 } 87 88 allocations, err = tx.CreateBucket(allocationsBucket) 89 if err != nil { 90 return nil, err 91 } 92 } 93 94 // Retrieve the specific allocations bucket 95 key := []byte(allocID) 96 alloc := allocations.Bucket(key) 97 if alloc == nil { 98 if !w { 99 return nil, fmt.Errorf("Allocation bucket doesn't exist and transaction is not writable") 100 } 101 102 alloc, err = allocations.CreateBucket(key) 103 if err != nil { 104 return nil, err 105 } 106 } 107 108 return alloc, nil 109 } 110 111 func allocationBucketExists(tx *bolt.Tx, allocID string) bool { 112 allocations := tx.Bucket(allocationsBucket) 113 if allocations == nil { 114 return false 115 } 116 117 // Retrieve the specific allocations bucket 118 alloc := allocations.Bucket([]byte(allocID)) 119 return alloc != nil 120 } 121 122 // getTaskBucket returns the bucket used to persist state about a 123 // particular task. If the root allocation bucket, the specific 124 // allocation or task bucket doesn't exist, they will be created as long as the 125 // transaction is writable. 126 func getTaskBucket(tx *bolt.Tx, allocID, taskName string) (*bolt.Bucket, error) { 127 alloc, err := getAllocationBucket(tx, allocID) 128 if err != nil { 129 return nil, err 130 } 131 132 // Retrieve the specific task bucket 133 w := tx.Writable() 134 key := []byte(taskName) 135 task := alloc.Bucket(key) 136 if task == nil { 137 if !w { 138 return nil, fmt.Errorf("Task bucket doesn't exist and transaction is not writable") 139 } 140 141 task, err = alloc.CreateBucket(key) 142 if err != nil { 143 return nil, err 144 } 145 } 146 147 return task, nil 148 } 149 150 // deleteAllocationBucket is used to delete an allocation bucket if it exists. 151 func deleteAllocationBucket(tx *bolt.Tx, allocID string) error { 152 if !tx.Writable() { 153 return fmt.Errorf("transaction must be writable") 154 } 155 156 // Retrieve the root allocations bucket 157 allocations := tx.Bucket(allocationsBucket) 158 if allocations == nil { 159 return nil 160 } 161 162 // Check if the bucket exists 163 key := []byte(allocID) 164 if allocBkt := allocations.Bucket(key); allocBkt == nil { 165 return nil 166 } 167 168 return allocations.DeleteBucket(key) 169 } 170 171 // deleteTaskBucket is used to delete a task bucket if it exists. 172 func deleteTaskBucket(tx *bolt.Tx, allocID, taskName string) error { 173 if !tx.Writable() { 174 return fmt.Errorf("transaction must be writable") 175 } 176 177 // Retrieve the root allocations bucket 178 allocations := tx.Bucket(allocationsBucket) 179 if allocations == nil { 180 return nil 181 } 182 183 // Retrieve the specific allocations bucket 184 alloc := allocations.Bucket([]byte(allocID)) 185 if alloc == nil { 186 return nil 187 } 188 189 // Check if the bucket exists 190 key := []byte(taskName) 191 if taskBkt := alloc.Bucket(key); taskBkt == nil { 192 return nil 193 } 194 195 return alloc.DeleteBucket(key) 196 } 197 198 func getAllAllocationIDs(tx *bolt.Tx) ([]string, error) { 199 allocationsBkt := tx.Bucket(allocationsBucket) 200 if allocationsBkt == nil { 201 return nil, nil 202 } 203 204 // Create a cursor for iteration. 205 var allocIDs []string 206 c := allocationsBkt.Cursor() 207 208 // Iterate over all the buckets 209 for k, _ := c.First(); k != nil; k, _ = c.Next() { 210 allocIDs = append(allocIDs, string(k)) 211 } 212 213 return allocIDs, nil 214 }