HADOOP-12097. Allow port range to be specified while starting webapp. Contributed by Varun Saxena.
This commit is contained in:
parent
d401e63b6c
commit
cce35c3815
@ -1887,6 +1887,18 @@ public String toString() {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get range start for the first integer range.
|
||||||
|
* @return range start.
|
||||||
|
*/
|
||||||
|
public int getRangeStart() {
|
||||||
|
if (ranges == null || ranges.isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
Range r = ranges.get(0);
|
||||||
|
return r.start;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Integer> iterator() {
|
public Iterator<Integer> iterator() {
|
||||||
return new RangeNumberIterator(ranges);
|
return new RangeNumberIterator(ranges);
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
import org.apache.hadoop.classification.InterfaceStability;
|
import org.apache.hadoop.classification.InterfaceStability;
|
||||||
import org.apache.hadoop.conf.ConfServlet;
|
import org.apache.hadoop.conf.ConfServlet;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.conf.Configuration.IntegerRanges;
|
||||||
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
||||||
import org.apache.hadoop.jmx.JMXJsonServlet;
|
import org.apache.hadoop.jmx.JMXJsonServlet;
|
||||||
import org.apache.hadoop.log.LogLevel;
|
import org.apache.hadoop.log.LogLevel;
|
||||||
@ -151,6 +152,7 @@ public final class HttpServer2 implements FilterContainer {
|
|||||||
|
|
||||||
protected final WebAppContext webAppContext;
|
protected final WebAppContext webAppContext;
|
||||||
protected final boolean findPort;
|
protected final boolean findPort;
|
||||||
|
protected final IntegerRanges portRanges;
|
||||||
private final Map<ServletContextHandler, Boolean> defaultContexts =
|
private final Map<ServletContextHandler, Boolean> defaultContexts =
|
||||||
new HashMap<>();
|
new HashMap<>();
|
||||||
protected final List<String> filterNames = new ArrayList<>();
|
protected final List<String> filterNames = new ArrayList<>();
|
||||||
@ -189,6 +191,7 @@ public static class Builder {
|
|||||||
private String keyPassword;
|
private String keyPassword;
|
||||||
|
|
||||||
private boolean findPort;
|
private boolean findPort;
|
||||||
|
private IntegerRanges portRanges = null;
|
||||||
|
|
||||||
private String hostName;
|
private String hostName;
|
||||||
private boolean disallowFallbackToRandomSignerSecretProvider;
|
private boolean disallowFallbackToRandomSignerSecretProvider;
|
||||||
@ -261,6 +264,11 @@ public Builder setFindPort(boolean findPort) {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder setPortRanges(IntegerRanges ranges) {
|
||||||
|
this.portRanges = ranges;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setConf(Configuration conf) {
|
public Builder setConf(Configuration conf) {
|
||||||
this.conf = conf;
|
this.conf = conf;
|
||||||
return this;
|
return this;
|
||||||
@ -496,6 +504,7 @@ private HttpServer2(final Builder b) throws IOException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.findPort = b.findPort;
|
this.findPort = b.findPort;
|
||||||
|
this.portRanges = b.portRanges;
|
||||||
initializeWebServer(b.name, b.hostName, b.conf, b.pathSpecs);
|
initializeWebServer(b.name, b.hostName, b.conf, b.pathSpecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1079,6 +1088,93 @@ private void loadListeners() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind listener by closing and opening the listener.
|
||||||
|
* @param listener
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private static void bindListener(ServerConnector listener) throws Exception {
|
||||||
|
// jetty has a bug where you can't reopen a listener that previously
|
||||||
|
// failed to open w/o issuing a close first, even if the port is changed
|
||||||
|
listener.close();
|
||||||
|
listener.open();
|
||||||
|
LOG.info("Jetty bound to port " + listener.getLocalPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create bind exception by wrapping the bind exception thrown.
|
||||||
|
* @param listener
|
||||||
|
* @param ex
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static BindException constructBindException(ServerConnector listener,
|
||||||
|
BindException ex) {
|
||||||
|
BindException be = new BindException("Port in use: "
|
||||||
|
+ listener.getHost() + ":" + listener.getPort());
|
||||||
|
if (ex != null) {
|
||||||
|
be.initCause(ex);
|
||||||
|
}
|
||||||
|
return be;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind using single configured port. If findPort is true, we will try to bind
|
||||||
|
* after incrementing port till a free port is found.
|
||||||
|
* @param listener jetty listener.
|
||||||
|
* @param port port which is set in the listener.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private void bindForSinglePort(ServerConnector listener, int port)
|
||||||
|
throws Exception {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
bindListener(listener);
|
||||||
|
break;
|
||||||
|
} catch (BindException ex) {
|
||||||
|
if (port == 0 || !findPort) {
|
||||||
|
throw constructBindException(listener, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// try the next port number
|
||||||
|
listener.setPort(++port);
|
||||||
|
Thread.sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind using port ranges. Keep on looking for a free port in the port range
|
||||||
|
* and throw a bind exception if no port in the configured range binds.
|
||||||
|
* @param listener jetty listener.
|
||||||
|
* @param startPort initial port which is set in the listener.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private void bindForPortRange(ServerConnector listener, int startPort)
|
||||||
|
throws Exception {
|
||||||
|
BindException bindException = null;
|
||||||
|
try {
|
||||||
|
bindListener(listener);
|
||||||
|
return;
|
||||||
|
} catch (BindException ex) {
|
||||||
|
// Ignore exception.
|
||||||
|
bindException = ex;
|
||||||
|
}
|
||||||
|
for(Integer port : portRanges) {
|
||||||
|
if (port == startPort) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Thread.sleep(100);
|
||||||
|
listener.setPort(port);
|
||||||
|
try {
|
||||||
|
bindListener(listener);
|
||||||
|
return;
|
||||||
|
} catch (BindException ex) {
|
||||||
|
// Ignore exception. Move to next port.
|
||||||
|
bindException = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw constructBindException(listener, bindException);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the main listener for the server
|
* Open the main listener for the server
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
@ -1091,25 +1187,10 @@ void openListeners() throws Exception {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int port = listener.getPort();
|
int port = listener.getPort();
|
||||||
while (true) {
|
if (portRanges != null && port != 0) {
|
||||||
// jetty has a bug where you can't reopen a listener that previously
|
bindForPortRange(listener, port);
|
||||||
// failed to open w/o issuing a close first, even if the port is changed
|
} else {
|
||||||
try {
|
bindForSinglePort(listener, port);
|
||||||
listener.close();
|
|
||||||
listener.open();
|
|
||||||
LOG.info("Jetty bound to port " + listener.getLocalPort());
|
|
||||||
break;
|
|
||||||
} catch (BindException ex) {
|
|
||||||
if (port == 0 || !findPort) {
|
|
||||||
BindException be = new BindException("Port in use: "
|
|
||||||
+ listener.getHost() + ":" + listener.getPort());
|
|
||||||
be.initCause(ex);
|
|
||||||
throw be;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// try the next port number
|
|
||||||
listener.setPort(++port);
|
|
||||||
Thread.sleep(100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,12 @@
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.conf.Configuration.IntegerRanges;
|
||||||
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
||||||
import org.apache.hadoop.http.HttpServer2.QuotingInputFilter.RequestQuoter;
|
import org.apache.hadoop.http.HttpServer2.QuotingInputFilter.RequestQuoter;
|
||||||
import org.apache.hadoop.http.resource.JerseyResource;
|
import org.apache.hadoop.http.resource.JerseyResource;
|
||||||
import org.apache.hadoop.net.NetUtils;
|
import org.apache.hadoop.net.NetUtils;
|
||||||
|
import org.apache.hadoop.net.ServerSocketUtil;
|
||||||
import org.apache.hadoop.security.Groups;
|
import org.apache.hadoop.security.Groups;
|
||||||
import org.apache.hadoop.security.ShellBasedUnixGroupsMapping;
|
import org.apache.hadoop.security.ShellBasedUnixGroupsMapping;
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
@ -644,4 +646,40 @@ public void testNoCacheHeader() throws Exception {
|
|||||||
assertNotNull(conn.getHeaderField("Date"));
|
assertNotNull(conn.getHeaderField("Date"));
|
||||||
assertEquals(conn.getHeaderField("Expires"), conn.getHeaderField("Date"));
|
assertEquals(conn.getHeaderField("Expires"), conn.getHeaderField("Date"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void stopHttpServer(HttpServer2 server) throws Exception {
|
||||||
|
if (server != null) {
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPortRanges() throws Exception {
|
||||||
|
Configuration conf = new Configuration();
|
||||||
|
int port = ServerSocketUtil.waitForPort(49000, 60);
|
||||||
|
int endPort = 49500;
|
||||||
|
conf.set("abc", "49000-49500");
|
||||||
|
HttpServer2.Builder builder = new HttpServer2.Builder()
|
||||||
|
.setName("test").setConf(new Configuration()).setFindPort(false);
|
||||||
|
IntegerRanges ranges = conf.getRange("abc", "");
|
||||||
|
int startPort = 0;
|
||||||
|
if (ranges != null && !ranges.isEmpty()) {
|
||||||
|
startPort = ranges.getRangeStart();
|
||||||
|
builder.setPortRanges(ranges);
|
||||||
|
}
|
||||||
|
builder.addEndpoint(URI.create("http://localhost:" + startPort));
|
||||||
|
HttpServer2 myServer = builder.build();
|
||||||
|
HttpServer2 myServer2 = null;
|
||||||
|
try {
|
||||||
|
myServer.start();
|
||||||
|
assertEquals(port, myServer.getConnectorAddress(0).getPort());
|
||||||
|
myServer2 = builder.build();
|
||||||
|
myServer2.start();
|
||||||
|
assertTrue(myServer2.getConnectorAddress(0).getPort() > port &&
|
||||||
|
myServer2.getConnectorAddress(0).getPort() <= endPort);
|
||||||
|
} finally {
|
||||||
|
stopHttpServer(myServer);
|
||||||
|
stopHttpServer(myServer2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
import org.apache.hadoop.classification.InterfaceAudience;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.conf.Configuration.IntegerRanges;
|
||||||
import org.apache.hadoop.http.HttpConfig.Policy;
|
import org.apache.hadoop.http.HttpConfig.Policy;
|
||||||
import org.apache.hadoop.http.HttpServer2;
|
import org.apache.hadoop.http.HttpServer2;
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
@ -92,6 +93,7 @@ static class ServletStruct {
|
|||||||
boolean findPort = false;
|
boolean findPort = false;
|
||||||
Configuration conf;
|
Configuration conf;
|
||||||
Policy httpPolicy = null;
|
Policy httpPolicy = null;
|
||||||
|
String portRangeConfigKey = null;
|
||||||
boolean devMode = false;
|
boolean devMode = false;
|
||||||
private String spnegoPrincipalKey;
|
private String spnegoPrincipalKey;
|
||||||
private String spnegoKeytabKey;
|
private String spnegoKeytabKey;
|
||||||
@ -157,6 +159,19 @@ public Builder<T> withHttpPolicy(Configuration conf, Policy httpPolicy) {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set port range config key and associated configuration object.
|
||||||
|
* @param config configuration.
|
||||||
|
* @param portRangeConfKey port range config key.
|
||||||
|
* @return builder object.
|
||||||
|
*/
|
||||||
|
public Builder<T> withPortRange(Configuration config,
|
||||||
|
String portRangeConfKey) {
|
||||||
|
this.conf = config;
|
||||||
|
this.portRangeConfigKey = portRangeConfKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder<T> withHttpSpnegoPrincipalKey(String spnegoPrincipalKey) {
|
public Builder<T> withHttpSpnegoPrincipalKey(String spnegoPrincipalKey) {
|
||||||
this.spnegoPrincipalKey = spnegoPrincipalKey;
|
this.spnegoPrincipalKey = spnegoPrincipalKey;
|
||||||
return this;
|
return this;
|
||||||
@ -265,15 +280,24 @@ public void setup() {
|
|||||||
: WebAppUtils.HTTP_PREFIX;
|
: WebAppUtils.HTTP_PREFIX;
|
||||||
}
|
}
|
||||||
HttpServer2.Builder builder = new HttpServer2.Builder()
|
HttpServer2.Builder builder = new HttpServer2.Builder()
|
||||||
.setName(name)
|
.setName(name).setConf(conf).setFindPort(findPort)
|
||||||
.addEndpoint(
|
|
||||||
URI.create(httpScheme + bindAddress
|
|
||||||
+ ":" + port)).setConf(conf).setFindPort(findPort)
|
|
||||||
.setACL(new AccessControlList(conf.get(
|
.setACL(new AccessControlList(conf.get(
|
||||||
YarnConfiguration.YARN_ADMIN_ACL,
|
YarnConfiguration.YARN_ADMIN_ACL,
|
||||||
YarnConfiguration.DEFAULT_YARN_ADMIN_ACL)))
|
YarnConfiguration.DEFAULT_YARN_ADMIN_ACL)))
|
||||||
.setPathSpec(pathList.toArray(new String[0]));
|
.setPathSpec(pathList.toArray(new String[0]));
|
||||||
|
// Get port ranges from config.
|
||||||
|
IntegerRanges ranges = null;
|
||||||
|
if (portRangeConfigKey != null) {
|
||||||
|
ranges = conf.getRange(portRangeConfigKey, "");
|
||||||
|
}
|
||||||
|
int startPort = port;
|
||||||
|
if (ranges != null && !ranges.isEmpty()) {
|
||||||
|
// Set port ranges if its configured.
|
||||||
|
startPort = ranges.getRangeStart();
|
||||||
|
builder.setPortRanges(ranges);
|
||||||
|
}
|
||||||
|
builder.addEndpoint(URI.create(httpScheme + bindAddress +
|
||||||
|
":" + startPort));
|
||||||
boolean hasSpnegoConf = spnegoPrincipalKey != null
|
boolean hasSpnegoConf = spnegoPrincipalKey != null
|
||||||
&& conf.get(spnegoPrincipalKey) != null && spnegoKeytabKey != null
|
&& conf.get(spnegoPrincipalKey) != null && spnegoKeytabKey != null
|
||||||
&& conf.get(spnegoKeytabKey) != null;
|
&& conf.get(spnegoKeytabKey) != null;
|
||||||
|
@ -35,6 +35,8 @@
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import org.apache.commons.lang.ArrayUtils;
|
import org.apache.commons.lang.ArrayUtils;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.net.ServerSocketUtil;
|
||||||
import org.apache.hadoop.yarn.MockApps;
|
import org.apache.hadoop.yarn.MockApps;
|
||||||
import org.apache.hadoop.yarn.webapp.view.HtmlPage;
|
import org.apache.hadoop.yarn.webapp.view.HtmlPage;
|
||||||
import org.apache.hadoop.yarn.webapp.view.JQueryUI;
|
import org.apache.hadoop.yarn.webapp.view.JQueryUI;
|
||||||
@ -307,6 +309,50 @@ public void setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void stopWebApp(WebApp app) {
|
||||||
|
if (app != null) {
|
||||||
|
app.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPortRanges() throws Exception {
|
||||||
|
WebApp app = WebApps.$for("test", this).start();
|
||||||
|
String baseUrl = baseUrl(app);
|
||||||
|
WebApp app1 = null;
|
||||||
|
WebApp app2 = null;
|
||||||
|
WebApp app3 = null;
|
||||||
|
WebApp app4 = null;
|
||||||
|
WebApp app5 = null;
|
||||||
|
try {
|
||||||
|
int port = ServerSocketUtil.waitForPort(48000, 60);
|
||||||
|
assertEquals("foo", getContent(baseUrl +"test/foo").trim());
|
||||||
|
app1 = WebApps.$for("test", this).at(port).start();
|
||||||
|
assertEquals(port, app1.getListenerAddress().getPort());
|
||||||
|
app2 = WebApps.$for("test", this).at("0.0.0.0",port, true).start();
|
||||||
|
assertTrue(app2.getListenerAddress().getPort() > port);
|
||||||
|
Configuration conf = new Configuration();
|
||||||
|
port = ServerSocketUtil.waitForPort(47000, 60);
|
||||||
|
app3 = WebApps.$for("test", this).at(port).withPortRange(conf, "abc").
|
||||||
|
start();
|
||||||
|
assertEquals(port, app3.getListenerAddress().getPort());
|
||||||
|
ServerSocketUtil.waitForPort(46000, 60);
|
||||||
|
conf.set("abc", "46000-46500");
|
||||||
|
app4 = WebApps.$for("test", this).at(port).withPortRange(conf, "abc").
|
||||||
|
start();
|
||||||
|
assertEquals(46000, app4.getListenerAddress().getPort());
|
||||||
|
app5 = WebApps.$for("test", this).withPortRange(conf, "abc").start();
|
||||||
|
assertTrue(app5.getListenerAddress().getPort() > 46000);
|
||||||
|
} finally {
|
||||||
|
stopWebApp(app);
|
||||||
|
stopWebApp(app1);
|
||||||
|
stopWebApp(app2);
|
||||||
|
stopWebApp(app3);
|
||||||
|
stopWebApp(app4);
|
||||||
|
stopWebApp(app5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static String baseUrl(WebApp app) {
|
static String baseUrl(WebApp app) {
|
||||||
return "http://localhost:"+ app.port() +"/";
|
return "http://localhost:"+ app.port() +"/";
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user