Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add PortAllocationManager#allocateConsecutivePortRange to allow multiple
ports to be allocated together within a specific range of ports.
This forms part of the fix for JENKINS-12821 in android-emulator-plugin..
  • Loading branch information
oldelvet committed Feb 15, 2013
1 parent 0c7d3b5 commit 007ca26
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 0 deletions.
Expand Up @@ -9,6 +9,7 @@
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.WeakHashMap;


Expand All @@ -21,6 +22,8 @@
public final class PortAllocationManager {
private final Computer node;

/** Maximum number of tries to allocate a specific port range. */
private static final int MAX_TRIES = 100;

/**
* Ports currently in use, to the build that uses it.
Expand Down Expand Up @@ -57,6 +60,67 @@ public synchronized int allocateRandom(AbstractBuild owner, int prefPort) throws
return i;
}

/**
* Allocate a continuous range of ports within specified limits.
* The caller is responsible for freeing the individual ports within
* the allocated range.
* @param portAllocator
* @param build the current build
* @param start the first in the range of allowable ports
* @param end the last entry in the range of allowable ports
* @param count the number of ports to allocate
* @param isConsecutive true if the allocated ports should be consecutive
* @return the ports allocated
* @throws InterruptedException if the allocation was interrupted
* @throws IOException if the allocation failed
*/
public int[] allocatePortRange(
final AbstractBuild owner,
int start, int end, int count, boolean isConsecutive)
throws InterruptedException, IOException {
int[] allocated = new int[count];

boolean allocationFailed = true;
Random rnd = new Random();

// Attempt the whole allocation a few times using a brute force approach.
for (int trynum = 0; (allocationFailed && (trynum < MAX_TRIES)); trynum++) {
allocationFailed = false;

// Allocate all of the ports in the range
for (int offset = 0; offset < count; offset++) {

final int requestedPort;
if (!isConsecutive || (offset == 0)) {
requestedPort = rnd.nextInt((end - start) - count) + start;
} else {
requestedPort = allocated[0] + offset;
}
try {
final int i;
synchronized (this) {
i = allocatePort(requestedPort);
ports.put(i, owner);
}
allocated[offset] = i;
} catch (PortUnavailableException ex) {
// Did not get requested port
allocationFailed = true;
// Free off allocated ports ready to try again
for (int freeOffset = offset - 1; freeOffset >= 0; freeOffset--) {
free(allocated[freeOffset]);
}
// Try again from the beginning.
break;
}
}
}
if (allocationFailed) {
throw new IOException("Failed to allocate port range");
}
return allocated;
}

/**
* Assigns the requested port.
*
Expand Down
Expand Up @@ -8,6 +8,7 @@
import hudson.remoting.VirtualChannel;

import org.jvnet.hudson.plugins.port_allocator.PortAllocationManager;
import org.jvnet.hudson.plugins.port_allocator.PortAllocationManager.PortUnavailableException;
import org.mockito.Mockito;

import junit.framework.TestCase;
Expand Down Expand Up @@ -131,4 +132,44 @@ public void testAllocateRandom() throws Throwable {
// Ensure that free port continues without problems.
manager.free(port);
}

/**
* Ensure that port allocation invokes the remote callable and
* passes back the port allocated by the remote node.
* @throws Throwable
*/
public void testAllocatePortRange() throws Throwable {
final VirtualChannel channel = Mockito.mock(VirtualChannel.class);
final Computer computer = Mockito.mock(Computer.class);
final AbstractBuild build = Mockito.mock(AbstractBuild.class);

final int mockStart = 42;
final int mockEnd = 48;
final int mockPort = 44;
Mockito.when(computer.getChannel()).thenReturn(channel);

Mockito.when(channel.call(Mockito.isNotNull(Callable.class)))
// First port succeeds
.thenReturn(mockPort)
// Second port fails
.thenThrow(PortUnavailableException.class)
// Better luck next time around
.thenReturn(mockPort + 2)
.thenReturn(mockPort + 3);

final PortAllocationManager manager = PortAllocationManager.getManager(computer);

int[] ports = manager.allocatePortRange(build, mockStart, mockEnd, 2, true);
assertNotNull(ports);
assertEquals(2, ports.length);
assertEquals(mockPort + 2, ports[0]);
assertEquals(mockPort + 3, ports[1]);
// We cannot confirm that second and subsequent allocations of port 42
// allocation will succeed because mocking allocatePort and/or Callable is
// difficult.

// Ensure that free port continues without problems.
manager.free(ports[0]);
manager.free(ports[1]);
}
}

0 comments on commit 007ca26

Please sign in to comment.