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  }