Merge pull request #1594 from thockin/ipallocator
Add an IPAllocator type
This commit is contained in:
commit
130784bfdf
153
pkg/registry/service/ip_allocator.go
Normal file
153
pkg/registry/service/ip_allocator.go
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
type ipAllocator struct {
|
||||
subnet *net.IPNet
|
||||
// TODO: This could be smarter, but for now a bitmap will suffice.
|
||||
lock sync.Mutex // protects 'used'
|
||||
used []byte // a bitmap of allocated IPs
|
||||
}
|
||||
|
||||
// newIPAllocator creates and intializes a new ipAllocator object.
|
||||
// FIXME: resync from storage at startup.
|
||||
func newIPAllocator(subnet *net.IPNet) *ipAllocator {
|
||||
if subnet == nil || subnet.IP == nil || subnet.Mask == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ones, bits := subnet.Mask.Size()
|
||||
numIps := 1 << uint(bits-ones)
|
||||
ipa := &ipAllocator{
|
||||
subnet: subnet,
|
||||
used: make([]byte, numIps/8),
|
||||
}
|
||||
ipa.used[0] = 0x01 // block the network addr
|
||||
ipa.used[(numIps/8)-1] = 0x80 // block the broadcast addr
|
||||
return ipa
|
||||
}
|
||||
|
||||
// Allocate allocates a specific IP. This is useful when recovering saved state.
|
||||
func (ipa *ipAllocator) Allocate(ip net.IP) error {
|
||||
ipa.lock.Lock()
|
||||
defer ipa.lock.Unlock()
|
||||
|
||||
if !ipa.subnet.Contains(ip) {
|
||||
return fmt.Errorf("IP %s does not fall within subnet %s", ip, ipa.subnet)
|
||||
}
|
||||
offset := ipSub(ip, ipa.subnet.IP)
|
||||
i := offset / 8
|
||||
m := byte(1 << byte(offset%8))
|
||||
if ipa.used[i]&m != 0 {
|
||||
return fmt.Errorf("IP %s is already allocated", ip)
|
||||
}
|
||||
ipa.used[i] |= m
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllocateNext allocates and returns a new IP.
|
||||
func (ipa *ipAllocator) AllocateNext() (net.IP, error) {
|
||||
ipa.lock.Lock()
|
||||
defer ipa.lock.Unlock()
|
||||
|
||||
for i := range ipa.used {
|
||||
if ipa.used[i] != 0xff {
|
||||
freeMask := ^ipa.used[i]
|
||||
nextBit, err := ffs(freeMask)
|
||||
if err != nil {
|
||||
// If this happens, something really weird is going on.
|
||||
glog.Errorf("ffs(%#x) had an unexpected error: %s", freeMask, err)
|
||||
return nil, err
|
||||
}
|
||||
ipa.used[i] |= 1 << nextBit
|
||||
offset := (i * 8) + int(nextBit)
|
||||
ip := ipAdd(copyIP(ipa.subnet.IP), offset)
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("can't find a free IP in %s", ipa.subnet)
|
||||
}
|
||||
|
||||
// This is a really dumb implementation of find-first-set-bit.
|
||||
func ffs(val byte) (uint, error) {
|
||||
if val == 0 {
|
||||
return 0, fmt.Errorf("Can't find-first-set on 0")
|
||||
}
|
||||
i := uint(0)
|
||||
for ; i < 8 && (val&(1<<i) == 0); i++ {
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// Add an offset to an IP address - used for joining network addr and host addr parts.
|
||||
func ipAdd(ip net.IP, offset int) net.IP {
|
||||
for i := 0; offset > 0; i++ {
|
||||
add := offset % 256
|
||||
ip[len(ip)-1-i] += byte(add)
|
||||
offset >>= 8
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// Subtract two IPs, returning the difference as an offset - used or splitting an IP into
|
||||
// network addr and host addr parts.
|
||||
func ipSub(lhs, rhs net.IP) int {
|
||||
// If they are not the same length, normalize them. Make copies because net.IP is
|
||||
// a slice underneath. Sneaky sneaky.
|
||||
if len(lhs) != len(rhs) {
|
||||
lhs = copyIP(lhs).To16()
|
||||
rhs = copyIP(rhs).To16()
|
||||
}
|
||||
offset := 0
|
||||
for i := range lhs {
|
||||
offset *= 256
|
||||
offset += int(lhs[i] - rhs[i])
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
// Make a copy of a net.IP. It appears to be a value type, but it is actually defined as a
|
||||
// slice, so value assignment is shallow. Why does a poor dumb user like me need to know
|
||||
// this sort of implementation detail?
|
||||
func copyIP(in net.IP) net.IP {
|
||||
out := make(net.IP, len(in))
|
||||
copy(out, in)
|
||||
return out
|
||||
}
|
||||
|
||||
// Release de-allocates an IP.
|
||||
func (ipa *ipAllocator) Release(ip net.IP) error {
|
||||
ipa.lock.Lock()
|
||||
defer ipa.lock.Unlock()
|
||||
|
||||
if !ipa.subnet.Contains(ip) {
|
||||
return fmt.Errorf("IP %s does not fall within subnet %s", ip, ipa.subnet)
|
||||
}
|
||||
offset := ipSub(ip, ipa.subnet.IP)
|
||||
i := offset / 8
|
||||
m := byte(1 << byte(offset%8))
|
||||
ipa.used[i] &^= m
|
||||
return nil
|
||||
}
|
251
pkg/registry/service/ip_allocator_test.go
Normal file
251
pkg/registry/service/ip_allocator_test.go
Normal file
@ -0,0 +1,251 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
if newIPAllocator(nil) != nil {
|
||||
t.Errorf("expected nil")
|
||||
}
|
||||
if newIPAllocator(&net.IPNet{}) != nil {
|
||||
t.Errorf("expected nil")
|
||||
}
|
||||
_, ipnet, err := net.ParseCIDR("93.76.0.0/22")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
ipa := newIPAllocator(ipnet)
|
||||
if ipa == nil {
|
||||
t.Errorf("expected non-nil")
|
||||
}
|
||||
if len(ipa.used) != 128 { // a /22 has 1024 IPs, 8 per byte = 128
|
||||
t.Errorf("wrong size for ipa.used")
|
||||
}
|
||||
if ipa.used[0] != 0x01 {
|
||||
t.Errorf("network address was not reserved")
|
||||
}
|
||||
if ipa.used[127] != 0x80 {
|
||||
t.Errorf("broadcast address was not reserved")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocate(t *testing.T) {
|
||||
_, ipnet, _ := net.ParseCIDR("93.76.0.0/22")
|
||||
ipa := newIPAllocator(ipnet)
|
||||
|
||||
if err := ipa.Allocate(net.ParseIP("93.76.0.1")); err != nil {
|
||||
t.Errorf("expected success, got %s", err)
|
||||
}
|
||||
|
||||
if ipa.Allocate(net.ParseIP("93.76.0.1")) == nil {
|
||||
t.Errorf("expected failure")
|
||||
}
|
||||
|
||||
if ipa.Allocate(net.ParseIP("1.2.3.4")) == nil {
|
||||
t.Errorf("expected failure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateNext(t *testing.T) {
|
||||
_, ipnet, _ := net.ParseCIDR("93.76.0.0/22")
|
||||
ipa := newIPAllocator(ipnet)
|
||||
|
||||
ip1, err := ipa.AllocateNext()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !ip1.Equal(net.ParseIP("93.76.0.1")) {
|
||||
t.Errorf("expected 93.76.0.1, got %s", ip1)
|
||||
}
|
||||
|
||||
ip2, err := ipa.AllocateNext()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !ip2.Equal(net.ParseIP("93.76.0.2")) {
|
||||
t.Errorf("expected 93.76.0.2, got %s", ip2)
|
||||
}
|
||||
|
||||
// Burn a bunch of addresses.
|
||||
for i := 3; i < 256; i++ {
|
||||
ipa.AllocateNext()
|
||||
}
|
||||
|
||||
ip256, err := ipa.AllocateNext()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !ip256.Equal(net.ParseIP("93.76.1.0")) {
|
||||
t.Errorf("expected 93.76.1.0, got %s", ip256)
|
||||
}
|
||||
|
||||
ip257, err := ipa.AllocateNext()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !ip257.Equal(net.ParseIP("93.76.1.1")) {
|
||||
t.Errorf("expected 93.76.1.1, got %s", ip257)
|
||||
}
|
||||
|
||||
// Burn a bunch of addresses.
|
||||
for i := 258; i < 1022; i++ {
|
||||
ipa.AllocateNext()
|
||||
}
|
||||
|
||||
ip1022, err := ipa.AllocateNext()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !ip1022.Equal(net.ParseIP("93.76.3.254")) {
|
||||
t.Errorf("expected 93.76.3.254, got %s", ip1022)
|
||||
}
|
||||
|
||||
_, err = ipa.AllocateNext()
|
||||
if err == nil {
|
||||
t.Errorf("Expected nil - allocator is full")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelease(t *testing.T) {
|
||||
_, ipnet, _ := net.ParseCIDR("93.76.0.0/24")
|
||||
ipa := newIPAllocator(ipnet)
|
||||
|
||||
err := ipa.Release(net.ParseIP("1.2.3.4"))
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error")
|
||||
}
|
||||
|
||||
ip1, err := ipa.AllocateNext()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
ip2, err := ipa.AllocateNext()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = ipa.AllocateNext()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = ipa.Release(ip2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
ip4, err := ipa.AllocateNext()
|
||||
if !ip4.Equal(ip2) {
|
||||
t.Errorf("Expected %s, got %s", ip2, ip4)
|
||||
}
|
||||
|
||||
// Burn a bunch of addresses.
|
||||
for i := 4; i < 255; i++ {
|
||||
ipa.AllocateNext()
|
||||
}
|
||||
_, err = ipa.AllocateNext()
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error")
|
||||
}
|
||||
ipa.Release(ip1)
|
||||
|
||||
ip5, err := ipa.AllocateNext()
|
||||
if !ip5.Equal(ip1) {
|
||||
t.Errorf("Expected %s, got %s", ip1, ip5)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFFS(t *testing.T) {
|
||||
_, err := ffs(0)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
value byte
|
||||
expected uint
|
||||
}{
|
||||
{0x01, 0}, {0x02, 1}, {0x04, 2}, {0x08, 3},
|
||||
{0x10, 4}, {0x20, 5}, {0x40, 6}, {0x80, 7},
|
||||
{0x22, 1}, {0xa0, 5}, {0xfe, 1}, {0xff, 0},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
r, err := ffs(tc.value)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if r != tc.expected {
|
||||
t.Errorf("Expected %d, got %d", tc.expected, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAdd(t *testing.T) {
|
||||
testCases := []struct {
|
||||
ip string
|
||||
offset int
|
||||
expected string
|
||||
}{
|
||||
{"1.2.3.0", 0, "1.2.3.0"},
|
||||
{"1.2.3.0", 1, "1.2.3.1"},
|
||||
{"1.2.3.0", 255, "1.2.3.255"},
|
||||
{"1.2.3.0", 256, "1.2.4.0"},
|
||||
{"1.2.3.0", 257, "1.2.4.1"},
|
||||
{"1.2.3.0", 65536, "1.3.3.0"},
|
||||
{"1.2.3.4", 1, "1.2.3.5"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
r := ipAdd(net.ParseIP(tc.ip), tc.offset)
|
||||
if !r.Equal(net.ParseIP(tc.expected)) {
|
||||
t.Errorf("Expected %s, got %s", tc.expected, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPSub(t *testing.T) {
|
||||
testCases := []struct {
|
||||
lhs string
|
||||
rhs string
|
||||
expected int
|
||||
}{
|
||||
{"1.2.3.0", "1.2.3.0", 0},
|
||||
{"1.2.3.1", "1.2.3.0", 1},
|
||||
{"1.2.3.255", "1.2.3.0", 255},
|
||||
{"1.2.4.0", "1.2.3.0", 256},
|
||||
{"1.2.4.1", "1.2.3.0", 257},
|
||||
{"1.3.3.0", "1.2.3.0", 65536},
|
||||
{"1.2.3.5", "1.2.3.4", 1},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
r := ipSub(net.ParseIP(tc.lhs), net.ParseIP(tc.rhs))
|
||||
if r != tc.expected {
|
||||
t.Errorf("Expected %s, got %s", tc.expected, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyIP(t *testing.T) {
|
||||
ip1 := net.ParseIP("1.2.3.4")
|
||||
ip2 := copyIP(ip1)
|
||||
ip2[0]++
|
||||
if ip1[0] == ip2[0] {
|
||||
t.Errorf("copyIP did not copy")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user