Skip to content

Commit

Permalink
fabric8io#210: Jube uses port finder to allocate ports in a range sta…
Browse files Browse the repository at this point in the history
…rting from 48000
  • Loading branch information
davsclaus committed Dec 9, 2014
1 parent 8315800 commit d5a0e10
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 24 deletions.
9 changes: 9 additions & 0 deletions docs/faqConfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ The port number can be configured as an environment variable, for example to use
Alternative the port number can also be configured in the `env.sh` / `env.bat` script file, which setup the environment variables.


#### How do I configure the port range allocations for processes?

Jube allocate ports to processes using port ranges starting from 48000 to 65536.

The minimum starting port number can be configured, for example to start from 30000:

export JUBE_PORT_START=30000


#### How do I debug the Jube process?

You can enable remote debugging of the Jube process via the environment variable:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.nio.charset.Charset;

import com.google.common.io.Files;
import io.fabric8.jube.process.service.AvailablePortFinder;
import io.fabric8.jube.process.service.ProcessManagerService;
import io.fabric8.jube.util.JubeVersionUtils;
import io.hawt.aether.OpenMavenURL;
Expand Down Expand Up @@ -51,7 +52,7 @@ public void setUp() throws Exception {
installDir = new File(basedir + "/target/processes");
LOG.info("Installing processes to {}", installDir.getAbsolutePath());

processManagerService = new ProcessManagerService(installDir, null);
processManagerService = new ProcessManagerService(installDir, null, AvailablePortFinder.MIN_PORT_NUMBER);

String version = JubeVersionUtils.getReleaseVersion();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 io.fabric8.jube.process.service;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Finds currently available server ports.
*/
public class AvailablePortFinder {

/**
* The minimum server currentMinPort number for IPv4.
* We use port ranges starting from 48000
*/
public static final int MIN_PORT_NUMBER = 48000;

/**
* The maximum server currentMinPort number for IPv4.
*/
public static final int MAX_PORT_NUMBER = 65535;

private static final Logger LOG = LoggerFactory.getLogger(AvailablePortFinder.class);

/**
* We'll hold open the lowest port in this process
* so parallel processes won't use the same block
* of ports. They'll go up to the next block.
*/
private static ServerSocket LOCK;

/**
* Incremented to the next lowest available port when getNextAvailable() is called.
*/
private AtomicInteger currentMinPort;

/**
* Creates a new instance.
*/
public AvailablePortFinder(int minPortNumber) {
currentMinPort = new AtomicInteger(minPortNumber);

int port = minPortNumber;
ServerSocket ss = null;

while (ss == null) {
try {
ss = new ServerSocket(port);
} catch (Exception e) {
ss = null;
port += 200;
}
}
LOCK = ss;
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
LOCK.close();
} catch (Exception ex) {
//ignore
}
}
});
currentMinPort.set(port + 1);
}

/**
* Gets the next available port starting at the lowest number. This is the preferred
* method to use. The port return is immediately marked in use and doesn't rely on the caller actually opening
* the port.
*
* @throws IllegalArgumentException is thrown if the port number is out of range
* @throws java.util.NoSuchElementException if there are no ports available
* @return the available port
*/
public synchronized int getNextAvailable() {
int next = getNextAvailable(currentMinPort.get());
currentMinPort.set(next + 1);
return next;
}

/**
* Gets the next available port starting at a given from port.
*
* @param fromPort the from port to scan for availability
* @throws IllegalArgumentException is thrown if the port number is out of range
* @throws java.util.NoSuchElementException if there are no ports available
* @return the available port
*/
public synchronized int getNextAvailable(int fromPort) {
if (fromPort < currentMinPort.get() || fromPort > MAX_PORT_NUMBER) {
throw new IllegalArgumentException("From port number not in valid range: " + fromPort);
}

for (int i = fromPort; i <= MAX_PORT_NUMBER; i++) {
if (available(i)) {
LOG.info("getNextAvailable({}) -> {}", fromPort, i);
return i;
}
}

throw new NoSuchElementException("Could not find an available port above " + fromPort);
}

/**
* Checks to see if a specific port is available.
*
* @param port the port number to check for availability
* @return <tt>true</tt> if the port is available, or <tt>false</tt> if not
* @throws IllegalArgumentException is thrown if the port number is out of range
*/
public boolean available(int port) throws IllegalArgumentException {
if (port < currentMinPort.get() || port > MAX_PORT_NUMBER) {
throw new IllegalArgumentException("Invalid start currentMinPort: " + port);
}

ServerSocket ss = null;
DatagramSocket ds = null;
try {
ss = new ServerSocket(port);
ss.setReuseAddress(true);
ds = new DatagramSocket(port);
ds.setReuseAddress(true);
return true;
} catch (IOException e) {
// Do nothing
} finally {
if (ds != null) {
ds.close();
}

if (ss != null) {
try {
ss.close();
} catch (IOException e) {
/* should not be thrown */
}
}
}

return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,12 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.management.MBeanServer;
Expand All @@ -57,7 +53,6 @@
import io.fabric8.utils.Strings;
import io.fabric8.utils.Zips;
import io.hawt.aether.OpenMavenURL;
import io.hawt.util.Closeables;
import org.apache.deltaspike.core.api.config.ConfigProperty;
import org.apache.deltaspike.core.api.jmx.JmxManaged;
import org.apache.deltaspike.core.api.jmx.MBean;
Expand All @@ -75,6 +70,7 @@ public class ProcessManagerService implements ProcessManagerServiceMBean {
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessManagerService.class);
private static final String INSTALLED_BINARY = "install.bin";
private final String remoteRepositoryUrls;
private final AvailablePortFinder availablePortFinder;

private Executor executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("jube-process-manager-%s").build());
private File storageLocation;
Expand All @@ -85,16 +81,16 @@ public class ProcessManagerService implements ProcessManagerServiceMBean {
private SortedMap<String, Installation> installations = Maps.newTreeMap();

private MBeanServer mbeanServer;
private AtomicInteger fallbackPortGenerator = new AtomicInteger(30000);
private boolean isWindows;

@Inject
public ProcessManagerService(@ConfigProperty(name = "JUBE_PROCESS_DIR", defaultValue = "./processes") String storageLocation,
@ConfigProperty(name = "JUBE_REMOTE_MAVEN_REPOS", defaultValue = DEFAULT_MAVEN_REPOS) String remoteRepositoryUrls) throws MalformedObjectNameException, IOException {
this(new File(storageLocation), remoteRepositoryUrls);
@ConfigProperty(name = "JUBE_REMOTE_MAVEN_REPOS", defaultValue = DEFAULT_MAVEN_REPOS) String remoteRepositoryUrls,
@ConfigProperty(name = "JUBE_PORT_START", defaultValue = "" + AvailablePortFinder.MIN_PORT_NUMBER) int minPort) throws MalformedObjectNameException, IOException {
this(new File(storageLocation), remoteRepositoryUrls, minPort);
}

public ProcessManagerService(File storageLocation, String remoteRepositoryUrls) throws MalformedObjectNameException, IOException {
public ProcessManagerService(File storageLocation, String remoteRepositoryUrls, int minPort) throws MalformedObjectNameException, IOException {
// make sure the install directory path is absolute and compact as there can be troubles with having foo/./bar paths
String path = FilesHelper.compactPath(storageLocation.getAbsolutePath());
this.storageLocation = new File(path);
Expand All @@ -103,8 +99,11 @@ public ProcessManagerService(File storageLocation, String remoteRepositoryUrls)
}
this.remoteRepositoryUrls = remoteRepositoryUrls;
this.isWindows = System.getProperty("os.name").toLowerCase().contains("windows");
this.availablePortFinder = new AvailablePortFinder(minPort);

LOGGER.info("Using remote maven repositories: " + remoteRepositoryUrls + " to find Jube image zips");
LOGGER.info("Using process directory: {}", this.storageLocation);
LOGGER.info("Using port allocation range: {}-{}", minPort, AvailablePortFinder.MAX_PORT_NUMBER);
LOGGER.info("Using remote maven repositories to find Jube image zips: {}", remoteRepositoryUrls);

// lets find the largest number in the current directory as we are on startup
lastId = 0;
Expand Down Expand Up @@ -258,19 +257,7 @@ public static String getLocalHostName() throws UnknownHostException {
}

protected int allocatePortNumber(InstallOptions options, File nestedProcessDirectory, String key, String value) {
ServerSocket ss = null;
try {
String hostName = getLocalHostName();
int idGeneratorPort = 0;
ss = new ServerSocket(idGeneratorPort);
ss.setReuseAddress(true);
return ss.getLocalPort();
} catch (Exception e) {
LOGGER.warn("Failed to allocate port " + key + ". " + e, e);
return fallbackPortGenerator.incrementAndGet();
} finally {
Closeables.closeQuietly(ss);
}
return availablePortFinder.getNextAvailable();
}

protected void exportInstallDirEnvVar(InstallOptions options, File installDir, InstallContext installContext, ProcessConfig config) throws IOException {
Expand Down

0 comments on commit d5a0e10

Please sign in to comment.