github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/helper/localaddr/db.go (about) 1 package localaddr 2 3 import ( 4 "bytes" 5 "container/heap" 6 "encoding/binary" 7 "encoding/gob" 8 "fmt" 9 "log" 10 "math/rand" 11 "net" 12 "os" 13 "path/filepath" 14 "time" 15 16 "github.com/boltdb/bolt" 17 ) 18 19 var ( 20 boltLocalAddrBucket = []byte("localaddr") 21 boltBuckets = [][]byte{ 22 boltLocalAddrBucket, 23 } 24 25 boltVersionKey = []byte("version") 26 boltAddrMapKey = []byte("addr_map") 27 boltAddrHeapKey = []byte("addr_heap") 28 boltSubnetKey = []byte("subnet") 29 ) 30 31 var ( 32 boltDataVersion byte = 2 33 ) 34 35 var boltCidr *net.IPNet 36 37 func init() { 38 _, cidr, err := net.ParseCIDR("100.64.0.0/10") 39 if err != nil { 40 panic(err) 41 } 42 43 boltCidr = cidr 44 } 45 46 // DB is a database of local addresses, and provides operations to find 47 // the next available address, release an address, etc. 48 // 49 // DB will act as an LRU: if there are no available IP addresses, it will find 50 // the oldest IP address and give that to you. This is to combat the fact that 51 // the things that use IP addresses can often be killed outside of our control, 52 // and the oldest one is most likely to be stale. This should be an edge 53 // case. 54 // 55 // The first time DB is used, it will find a usable subnet space and 56 // allocate that as its own. After it allocates that space, it will use 57 // that for the duration of this DBs existence. The usable subnet space 58 // is randomized to try to make it unlikely to have a collision. 59 // 60 // DB uses a /24 so the entire space of available IP addresses is only 61 // 256, but these IPs are meant to be local, so they shouldn't overflow 62 // (it would mean more than 256 VMs are up... or that each of those VMs 63 // has a lot of network interfaces. Both cases are unlikely in Otto). 64 // 65 // FUTURE TODO: 66 // 67 // * Allocate additional subnets once we run out of IP space (vs. LRU) 68 // 69 type DB struct { 70 // Path is the path to the IP database. This file doesn't need to 71 // exist but needs to be a writable path. The parent directory will 72 // be made. 73 Path string 74 } 75 76 // Next returns the next IP that is not allocated. 77 func (this *DB) Next() (net.IP, error) { 78 db, err := this.db() 79 if err != nil { 80 return nil, err 81 } 82 defer db.Close() 83 84 var result net.IP 85 err = db.Update(func(tx *bolt.Tx) error { 86 bucket := tx.Bucket(boltLocalAddrBucket) 87 data := bucket.Get(boltSubnetKey) 88 if data == nil { 89 panic("no subnet") 90 } 91 92 // Get the existing IP addresses that we've mapped 93 addrMap, addrQ, err := this.getData(bucket) 94 if err != nil { 95 return err 96 } 97 98 // Parse the subnet 99 ip, ipnet, err := net.ParseCIDR(string(data)) 100 if err != nil { 101 return err 102 } 103 ip = ip.To4() 104 105 // Generate a random IP in our subnet and try to use it 106 found := false 107 for { 108 // Create a random address in the subnet 109 ipRaw := make([]byte, 4) 110 binary.LittleEndian.PutUint32(ipRaw, rand.Uint32()) 111 for i, v := range ipRaw { 112 ip[i] = ip[i] + (v &^ ipnet.Mask[i]) 113 } 114 115 // If this IP exists, then try again 116 if _, ok := addrMap[ip.String()]; ok { 117 continue 118 } 119 120 // We found an IP! 121 found = true 122 break 123 } 124 125 // If we didn't find an IP, we just use the oldest one available 126 if !found { 127 result = heap.Pop(&addrQ).(*ipEntry).Value 128 } 129 130 // Set the result 131 result = ip 132 133 // Add the IP to the map 134 entry := &ipEntry{LeaseTime: time.Now().UTC(), Value: ip} 135 heap.Push(&addrQ, entry) 136 addrMap[ip.String()] = entry.Index 137 138 // Store the data 139 return this.putData(bucket, addrMap, addrQ) 140 }) 141 142 return result, err 143 } 144 145 // Release releases the given IP, removing it from the database. 146 func (this *DB) Release(ip net.IP) error { 147 db, err := this.db() 148 if err != nil { 149 return err 150 } 151 defer db.Close() 152 153 return db.Update(func(tx *bolt.Tx) error { 154 bucket := tx.Bucket(boltLocalAddrBucket) 155 156 // Get the existing IP addresses that we've mapped 157 addrMap, addrQ, err := this.getData(bucket) 158 if err != nil { 159 return err 160 } 161 162 // If it isn't in there, we're done 163 idx, ok := addrMap[ip.String()] 164 if !ok { 165 return nil 166 } 167 168 // Delete and save 169 delete(addrMap, ip.String()) 170 addrQ, addrQ[len(addrQ)-1] = append(addrQ[:idx], addrQ[idx+1:]...), nil 171 heap.Init(&addrQ) 172 173 return this.putData(bucket, addrMap, addrQ) 174 }) 175 } 176 177 // Renew updates the last used time of the given IP to right now. 178 // 179 // This should be called whenever a DB-given IP is used to make sure 180 // it isn't chosen as the LRU if we run out of IPs. 181 func (this *DB) Renew(ip net.IP) error { 182 db, err := this.db() 183 if err != nil { 184 return err 185 } 186 defer db.Close() 187 188 return db.Update(func(tx *bolt.Tx) error { 189 bucket := tx.Bucket(boltLocalAddrBucket) 190 191 // Get the existing IP addresses that we've mapped 192 addrMap, addrQ, err := this.getData(bucket) 193 if err != nil { 194 return err 195 } 196 197 // If it isn't in there, we're done 198 idx, ok := addrMap[ip.String()] 199 if !ok { 200 return nil 201 } 202 203 entry := addrQ[idx] 204 entry.LeaseTime = time.Now().UTC() 205 addrQ.Update(entry) 206 207 return nil 208 }) 209 } 210 211 // db returns the database handle, and sets up the DB if it has never 212 // been created. 213 func (this *DB) db() (*bolt.DB, error) { 214 // Make the directory to store our DB 215 if err := os.MkdirAll(filepath.Dir(this.Path), 0755); err != nil { 216 return nil, err 217 } 218 219 // Create/Open the DB 220 db, err := bolt.Open(this.Path, 0644, nil) 221 if err != nil { 222 return nil, err 223 } 224 225 // Create the buckets 226 err = db.Update(func(tx *bolt.Tx) error { 227 for _, b := range boltBuckets { 228 if _, err := tx.CreateBucketIfNotExists(b); err != nil { 229 return err 230 } 231 } 232 233 return nil 234 }) 235 if err != nil { 236 return nil, err 237 } 238 239 // Check the DB version 240 var version byte 241 bootstrap := false 242 err = db.Update(func(tx *bolt.Tx) error { 243 bucket := tx.Bucket(boltLocalAddrBucket) 244 data := bucket.Get([]byte("version")) 245 if data == nil || len(data) == 0 { 246 version = boltDataVersion 247 bootstrap = true 248 return bucket.Put([]byte("version"), []byte{boltDataVersion}) 249 } 250 251 version = data[0] 252 return nil 253 }) 254 if err != nil { 255 return nil, err 256 } 257 258 if version > boltDataVersion { 259 return nil, fmt.Errorf( 260 "IP data version is higher than this version of Otto knows how\n"+ 261 "to handle! This version of Otto can read up to version %d,\n"+ 262 "but version %d data file found.\n\n"+ 263 "This means that a newer version of Otto touched this data,\n"+ 264 "or the data was corrupted in some other way.", 265 boltDataVersion, version) 266 } 267 268 // Map of update functions 269 updateMap := map[byte]func(*bolt.DB) error{ 270 1: this.v1_to_v2, 271 } 272 for version < boltDataVersion { 273 log.Printf( 274 "[INFO] upgrading lease DB from v%d to v%d", version, version+1) 275 err := updateMap[version](db) 276 if err != nil { 277 return nil, fmt.Errorf( 278 "Error upgrading data from v%d to v%d: %s", 279 version, version+1, err) 280 } 281 282 version++ 283 } 284 285 // Bootstrap if we have to 286 if bootstrap { 287 if err := this.v1_to_v2(db); err != nil { 288 return nil, err 289 } 290 } 291 292 // Just call the upgrade to init 293 return db, nil 294 } 295 296 func (this *DB) v1_to_v2(db *bolt.DB) error { 297 return db.Update(func(tx *bolt.Tx) error { 298 bucket := tx.Bucket(boltLocalAddrBucket) 299 300 // Replace the subnet with the fixed CIDR we're using. The old CIDR 301 // doesn't matter... 302 err := bucket.Put(boltSubnetKey, []byte(boltCidr.String())) 303 if err != nil { 304 return err 305 } 306 307 boltAddrBucket := []byte("addrs") 308 if b := bucket.Bucket(boltAddrBucket); b != nil { 309 // And delete all the addresses, we start over 310 err = bucket.DeleteBucket(boltAddrBucket) 311 if err != nil { 312 return err 313 } 314 } 315 316 return bucket.Put(boltVersionKey, []byte{byte(2)}) 317 }) 318 } 319 320 func (this *DB) putData( 321 bucket *bolt.Bucket, 322 addrMap map[string]int, 323 addrQ ipQueue) error { 324 var buf, buf2 bytes.Buffer 325 if err := gob.NewEncoder(&buf).Encode(addrMap); err != nil { 326 return err 327 } 328 if err := bucket.Put(boltAddrMapKey, buf.Bytes()); err != nil { 329 return err 330 } 331 332 if err := gob.NewEncoder(&buf2).Encode(addrQ); err != nil { 333 return err 334 } 335 if err := bucket.Put(boltAddrHeapKey, buf2.Bytes()); err != nil { 336 return err 337 } 338 339 return nil 340 } 341 342 func (this *DB) getData(bucket *bolt.Bucket) (map[string]int, ipQueue, error) { 343 var addrQ ipQueue 344 heapRaw := bucket.Get(boltAddrHeapKey) 345 if heapRaw == nil { 346 addrQ = ipQueue(make([]*ipEntry, 0, 1)) 347 } else { 348 dec := gob.NewDecoder(bytes.NewReader(heapRaw)) 349 if err := dec.Decode(&addrQ); err != nil { 350 return nil, nil, err 351 } 352 } 353 354 var addrMap map[string]int 355 mapRaw := bucket.Get(boltAddrMapKey) 356 if mapRaw == nil { 357 addrMap = make(map[string]int) 358 } else { 359 dec := gob.NewDecoder(bytes.NewReader(mapRaw)) 360 if err := dec.Decode(&addrMap); err != nil { 361 return nil, nil, err 362 } 363 } 364 365 return addrMap, addrQ, nil 366 }