Skip to content

Commit

Permalink
Fix AWS device allocator to only use valid device names
Browse files Browse the repository at this point in the history
According to
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/device_naming.html
we can only use /dev/xvd[b-c][a-z] as device names - so we can only
allocate upto 52 ebs volumes on a node.
  • Loading branch information
gnufied committed Feb 15, 2017
1 parent 44f345f commit 89d075f
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 64 deletions.
2 changes: 1 addition & 1 deletion pkg/cloudprovider/providers/aws/aws.go
Expand Up @@ -1225,7 +1225,7 @@ func (c *Cloud) getMountDevice(
if deviceAllocator == nil {
// we want device names with two significant characters, starting with
// /dev/xvdba (leaving xvda - xvdz and xvdaa-xvdaz to the system)
deviceAllocator = NewDeviceAllocator(2, "ba")
deviceAllocator = NewDeviceAllocator(0)
c.deviceAllocators[i.nodeName] = deviceAllocator
}
chosen, err := deviceAllocator.GetNext(deviceMappings)
Expand Down
54 changes: 23 additions & 31 deletions pkg/cloudprovider/providers/aws/device_allocator.go
Expand Up @@ -43,53 +43,45 @@ type DeviceAllocator interface {
}

type deviceAllocator struct {
firstDevice mountDevice
lastAssignedDevice mountDevice
length int
possibleDevices []mountDevice
lastIndex int
}

// NewDeviceAllocator creates new DeviceAlllocator that allocates device names
// of given length ("aaa" for length 3) and with given first device, so all
// devices before the first device are left to the operating system.
// With length 2 and firstDevice "ba", it will allocate device names
// ba, bb, ..., bz, ca, ... cz, ..., da, ... zz, so a..z and aa..az can be used
// by the operating system.
func NewDeviceAllocator(length int, firstDevice mountDevice) DeviceAllocator {
lastDevice := make([]byte, length)
for i := 0; i < length; i++ {
lastDevice[i] = 'z'
// Allocates device names according to scheme ba..bz, ca..cz
// it moves along the ring and always picks next device until
// device list is exhausted.
func NewDeviceAllocator(lastIndex int) DeviceAllocator {
possibleDevices := []mountDevice{}
for _, firstChar := range []rune{'b', 'c'} {
for i := 'a'; i <= 'z'; i++ {
dev := mountDevice([]rune{firstChar, i})
possibleDevices = append(possibleDevices, dev)
}
}
return &deviceAllocator{
firstDevice: firstDevice,
lastAssignedDevice: mountDevice(lastDevice),
length: length,
possibleDevices: possibleDevices,
lastIndex: lastIndex,
}
}

func (d *deviceAllocator) GetNext(existingDevices ExistingDevices) (mountDevice, error) {
candidate := d.lastAssignedDevice

var candidate mountDevice
foundIndex := d.lastIndex
for {
candidate = d.nextDevice(candidate)
candidate, foundIndex = d.nextDevice(candidate, foundIndex+1)
if _, found := existingDevices[candidate]; !found {
d.lastAssignedDevice = candidate
d.lastIndex = foundIndex
return candidate, nil
}
if candidate == d.lastAssignedDevice {
if foundIndex == d.lastIndex {
return "", fmt.Errorf("no devices are available")
}
}
}

func (d *deviceAllocator) nextDevice(device mountDevice) mountDevice {
dev := []byte(device)
for i := d.length - 1; i >= 0; i-- {
if dev[i] != 'z' {
dev[i]++
return mountDevice(dev)
}
dev[i] = 'a'
func (d *deviceAllocator) nextDevice(device mountDevice, nextIndex int) (mountDevice, int) {
if nextIndex < len(d.possibleDevices) {
return d.possibleDevices[nextIndex], nextIndex
}
// all parts of device were 'z', jump to the first device
return d.firstDevice
return d.possibleDevices[0], 0
}
45 changes: 13 additions & 32 deletions pkg/cloudprovider/providers/aws/device_allocator_test.go
Expand Up @@ -22,56 +22,37 @@ func TestDeviceAllocator(t *testing.T) {
tests := []struct {
name string
existingDevices ExistingDevices
length int
firstDevice mountDevice
lastAllocated mountDevice
lastIndex int
expectedOutput mountDevice
}{
{
"empty device list",
ExistingDevices{},
2,
"aa",
"aa",
"ab",
0,
"bb",
},
{
"empty device list with wrap",
ExistingDevices{},
2,
"ba",
"zz",
51,
"ba", // next to 'zz' is the first one, 'ba'
},
{
"device list",
ExistingDevices{"aa": "used", "ab": "used", "ac": "used"},
2,
"aa",
"aa",
"ad", // all up to "ac" are used
ExistingDevices{"ba": "used", "bb": "used", "bc": "used"},
0,
"bd",
},
{
"device list with wrap",
ExistingDevices{"zy": "used", "zz": "used", "ba": "used"},
2,
"ba",
"zx",
"bb", // "zy", "zz" and "ba" are used
},
{
"three characters with wrap",
ExistingDevices{"zzy": "used", "zzz": "used", "baa": "used"},
3,
"baa",
"zzx",
"bab",
ExistingDevices{"cy": "used", "cz": "used", "ba": "used"},
49,
"bb", // "cy", "cz" and "ba" are used
},
}

for _, test := range tests {
allocator := NewDeviceAllocator(test.length, test.firstDevice).(*deviceAllocator)
allocator.lastAssignedDevice = test.lastAllocated
allocator := NewDeviceAllocator(test.lastIndex).(*deviceAllocator)

got, err := allocator.GetNext(test.existingDevices)
if err != nil {
Expand All @@ -84,12 +65,12 @@ func TestDeviceAllocator(t *testing.T) {
}

func TestDeviceAllocatorError(t *testing.T) {
allocator := NewDeviceAllocator(2, "ba").(*deviceAllocator)
allocator := NewDeviceAllocator(0).(*deviceAllocator)
existingDevices := ExistingDevices{}

// make all devices used
var first, second byte
for first = 'b'; first <= 'z'; first++ {
for first = 'b'; first <= 'c'; first++ {
for second = 'a'; second <= 'z'; second++ {
device := [2]byte{first, second}
existingDevices[mountDevice(device[:])] = "used"
Expand Down

0 comments on commit 89d075f

Please sign in to comment.