diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea88496047403449aad3a6125de18020c608839e..ae012a30c68e3f326845ff405b6f3e0ed6781e11 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# embedmongo-maven-plugin Changelog
+## 0.1.8
+
+* Add support for random port allocation - new configuration option `randomPort`
+
## 0.1.7
* Add `logFile`/`logFileEncoding` configuration options (thanks @matthewadams)
diff --git a/README.md b/README.md
index d8a7079caf6ac339e3b23ff59b5190a6e670d701..1d441adda2f55b6a6ffa726403ec13e7c201f907 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ Usage
</goals>
<configuration>
<port>37017</port> <!-- optional, default 27017 -->
+ <randomPort>true</randomPort> <!-- optional, default is false, if true overrides "static" port configuration -->
<version>2.0.4</version> <!-- optional, default 2.2.1 -->
<databaseDirectory>/tmp/mongotest</databaseDirectory> <!-- optional, default is a new dir in java.io.tmpdir -->
<logging>file</logging> <!-- optional (file|console|none), default console -->
@@ -48,7 +49,8 @@ Notes
* By default, the `start` goal is bound to `pre-integration-test`, the `stop` goal is bound to `post-integration-test`. You can of course bind to different phases if required.
* If you omit/forget the `stop` goal, any Mongo process spawned by the `start` goal will be stopped when the JVM terminates.
-* If you want to run many Maven builds in parallel using Jenkins, try the [Port Allocator Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Port+Allocator+Plugin) to avoid port conflicts.
+* If you want to run many Maven builds in parallel you can use `randomPort` to avoid port conflicts.
+ If you are using Jenkins, you can also try the [Port Allocator Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Port+Allocator+Plugin).
* If you need to use a proxy to download MongoDB then you can either use `-Dhttp.proxyHost` and `-Dhttp.proxyPort` as additional Maven arguments (this will affect the entire build) or instruct the plugin to use a proxy when downloading Mongo by adding the `proxyHost` and `proxyPort` configuration properties.
* Using the `file` logging mode results in a new log file created at `./embedmongo.log` unless you specify via `<logFile>` (where `.` means the curent working directory, usually the same as `${basedir}`).
* If you'd like the start goal to start mongodb and wait, you can add `-Dembedmongo.wait` to your Maven command line arguments
diff --git a/pom.xml b/pom.xml
index 48a12fa74e98f4c281243931dba03b21f424561f..26eec03e4545a1e449ebe8ddc3e15802b3ed42e3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,6 +97,12 @@
<artifactId>maven-plugin-api</artifactId>
<version>2.0</version>
</dependency>
+ <!--To be able to use MavenSession#userProperties we need maven-core (version 2.0 is too old, however)-->
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-core</artifactId>
+ <version>2.2.1</version>
+ </dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-project</artifactId>
@@ -107,6 +113,12 @@
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>1.31</version>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit-dep</artifactId>
+ <version>4.10</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<profiles>
diff --git a/src/main/java/com/github/joelittlejohn/embedmongo/StartEmbeddedMongoMojo.java b/src/main/java/com/github/joelittlejohn/embedmongo/StartEmbeddedMongoMojo.java
index 76a7717adec7631f9582c986cf79352414344d60..2600afbf8a28ca2101561c5a252d01930a345101 100644
--- a/src/main/java/com/github/joelittlejohn/embedmongo/StartEmbeddedMongoMojo.java
+++ b/src/main/java/com/github/joelittlejohn/embedmongo/StartEmbeddedMongoMojo.java
@@ -28,8 +28,11 @@ import java.net.SocketAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.List;
+import java.util.Properties;
import java.util.concurrent.TimeUnit;
+import com.github.joelittlejohn.embedmongo.port.PortHelper;
+import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
@@ -53,6 +56,7 @@ import de.flapdoodle.embed.process.distribution.GenericVersion;
import de.flapdoodle.embed.process.distribution.IVersion;
import de.flapdoodle.embed.process.exceptions.DistributionException;
import de.flapdoodle.embed.process.runtime.Network;
+import org.apache.maven.project.MavenProject;
/**
* When invoked, this goal starts an instance of mongo. The required binaries
@@ -76,6 +80,14 @@ public class StartEmbeddedMongoMojo extends AbstractMojo {
*/
private int port;
+ /**
+ * Random port should be used for MongoDB instead of the one specified by {@code port}.
+ *
+ * @parameter expression="${embedmongo.randomPort}" default-value="false"
+ * @since 0.1.8
+ */
+ private boolean randomPort;
+
/**
* The version of MongoDB to run e.g. 2.1.1, 1.6 v1.8.2, V2_0_4,
*
@@ -161,6 +173,23 @@ public class StartEmbeddedMongoMojo extends AbstractMojo {
*/
private String proxyPassword;
+ /**
+ * The maven project.
+ *
+ * @parameter expression="${project}"
+ * @readonly
+ */
+ private MavenProject project;
+
+ /**
+ * The Maven Session Object for setting allocated port to session's user properties.
+ *
+ * @parameter expression="${session}"
+ * @readonly
+ */
+ private MavenSession session;
+
+
@Override
@SuppressWarnings("unchecked")
public void execute() throws MojoExecutionException, MojoFailureException {
@@ -176,7 +205,10 @@ public class StartEmbeddedMongoMojo extends AbstractMojo {
.defaults(Command.MongoD)
.processOutput(getOutputConfig())
.build();
-
+ if (randomPort) {
+ port = new PortHelper().allocateRandomPort();
+ }
+ savePortToSessionUserProperties();
MongodConfig mongoConfig = new MongodConfig(getVersion(),
new Net(bindIp, port, Network.localhostIsIPv6()),
new Storage(getDataDirectory(), null, 0),
@@ -208,6 +240,25 @@ public class StartEmbeddedMongoMojo extends AbstractMojo {
}
}
+ /**
+ * Saves port to the {@link MavenSession#userProperties} to provide client with ability to retrieve port
+ * later in integration-test-phase via {@link PortHelper#getMongoPort(String)} method.
+ * Port is saved as a property {@code MONGO_PORT_PROPERTY + "." + artifactId} where {@code artifactId}
+ * is id of project artifact where the integration tests are run.
+ * The {@code artifactId} suffix is necessary because concurrent executions of {@code embedmongo-maven-plugin}
+ * cannot share the same property.
+ * <p>
+ * {@code userProperties} seems to be the only way how to propagate property to the forked JVM run
+ * started by failsafe plugin.
+ * </p>
+ */
+ private void savePortToSessionUserProperties() {
+ final String portKey = PortHelper.MONGO_PORT_PROPERTY + "." + project.getArtifactId();
+ final String portValue = Integer.toString(port);
+ final Properties userProperties = session.getUserProperties();
+ userProperties.setProperty(portKey, portValue);
+ }
+
private ProcessOutput getOutputConfig() throws MojoFailureException {
LoggingStyle loggingStyle = LoggingStyle.valueOf(logging.toUpperCase());
diff --git a/src/main/java/com/github/joelittlejohn/embedmongo/port/PortHelper.java b/src/main/java/com/github/joelittlejohn/embedmongo/port/PortHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..303059879d3749690d9fed5e0c8afb52c86a2941
--- /dev/null
+++ b/src/main/java/com/github/joelittlejohn/embedmongo/port/PortHelper.java
@@ -0,0 +1,99 @@
+/**
+ * Copyright © 2012 Joe Littlejohn
+ *
+ * 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 com.github.joelittlejohn.embedmongo.port;
+
+import org.codehaus.plexus.util.StringUtils;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+/**
+ * Port helper for common operations with port.
+ * <p>
+ * It can :
+ * <ul>
+ * <li>allocate random free port - Inspired by
+ * <a href="https://github.com/sonatype/port-allocator-maven-plugin/blob/master/src/main/java/org/sonatype/plugins/portallocator/PortAllocatorMojo.java">
+ * PortAllocatorMojo</a>.</li>
+ * <li>Return port already allocated by invoking start goal of {@code embedmongo-maven-plugin}</li>
+ * </ul>
+
+* </p>
+ */
+public class PortHelper {
+
+ public static final String MONGO_PORT_PROPERTY = "embedmongo.port";
+
+
+ /**
+ * Finds port where the mongodb started in previous phase by {@code embedmongo-maven-plugin} should be running.
+ * This assumes that embedmongo-maven-plugin' goal start has already been invoked for {@code artifactId}
+ * and allocates port properly.
+ *
+ * @param artifactId maven artifact id of module for which the port set by {@code embedmongo-maven-plugin} should be found.
+ * @return port of mongodb for running integration tests of given {@code artifactId}
+ */
+ public static int getMongoPort(String artifactId) {
+ String portProperty;
+ if (artifactId == null || artifactId.length() == 0) {
+ throw new IllegalArgumentException("maven artifactId has to be specified to find port for proper maven module.");
+ } else {
+ portProperty = MONGO_PORT_PROPERTY + "." + artifactId;
+ }
+ final String port = System.getProperty(portProperty);
+ if (StringUtils.isEmpty(port)) {
+ throw new IllegalStateException("No mongo port has been set by embedmongo maven plugin via system property '"
+ + portProperty + "'!\n Check plugin configuration and make sure that mongoDb is started before"
+ + " you are trying to access its port.");
+ }
+ return Integer.valueOf(port);
+ }
+
+
+ /**
+ * Allocates new free random port.
+ *
+ * <p>
+ * This implementation allocate random free port and closes it immediately.
+ * In some (hopefully) rare situations the port may be occupied in the meantime between calling this method
+ * and using returned port on client side.
+ * </p>
+ * @return random free port
+ */
+ public int allocateRandomPort() {
+ return allocate(0);
+ }
+
+
+ //--------------------------------------------------- HELPER METHODS -----------------------------------------------
+
+ private static int allocate(int portNumber) {
+ ServerSocket server;
+ try {
+ server = new ServerSocket(portNumber);
+ } catch (IOException e) {
+ throw new PortUnavailableException(portNumber, e);
+ }
+
+ portNumber = server.getLocalPort();
+ try {
+ server.close();
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to release port " + portNumber, e);
+ }
+ return portNumber;
+ }
+}
diff --git a/src/main/java/com/github/joelittlejohn/embedmongo/port/PortUnavailableException.java b/src/main/java/com/github/joelittlejohn/embedmongo/port/PortUnavailableException.java
new file mode 100644
index 0000000000000000000000000000000000000000..2add5af195cd7bb91bd2afac802edc8fd38fad54
--- /dev/null
+++ b/src/main/java/com/github/joelittlejohn/embedmongo/port/PortUnavailableException.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright © 2012 Joe Littlejohn
+ *
+ * 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 com.github.joelittlejohn.embedmongo.port;
+
+/**
+ * Represents exceptional state when given port is not available.
+ */
+public class PortUnavailableException extends RuntimeException {
+ public PortUnavailableException(int port, Throwable cause) {
+ super("Port " + port + " is not available", cause);
+ }
+}
diff --git a/src/test/java/com/github/joelittlejohn/embedmongo/port/PortHelperTest.java b/src/test/java/com/github/joelittlejohn/embedmongo/port/PortHelperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa406cc27b83831b9cf0c420544ca414ea8a0b5a
--- /dev/null
+++ b/src/test/java/com/github/joelittlejohn/embedmongo/port/PortHelperTest.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright © 2012 Joe Littlejohn
+ *
+ * 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 com.github.joelittlejohn.embedmongo.port;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class PortHelperTest {
+
+ private final PortHelper portHelper = new PortHelper();
+ private final ScheduledExecutorService testPooledExecutor = Executors.newScheduledThreadPool(20);
+
+ @After
+ public void tearDown() throws Exception {
+ testPooledExecutor.shutdown();
+ }
+
+ /**
+ * This test executes method {@link com.github.joelittlejohn.embedmongo.port.PortHelper#allocateRandomPort()} many times
+ * concurrently to make sure that port allocation works correctly under stress.
+ */
+ @Test
+ public void testAllocateRandomPort() throws Exception {
+ final int testAllocationCount = 10000;
+ final CountDownLatch allocationsCounter = new CountDownLatch(testAllocationCount);
+
+ final Runnable allocatePort = new Runnable() {
+ @Override
+ public void run() {
+ int port = -1;
+ try {
+ port = portHelper.allocateRandomPort();
+ new ServerSocket(port);
+ // port has been bind successfully
+ } catch (IOException e) {
+ throw new RuntimeException("Port " + port + " cannot be bind!");
+ } finally {
+ allocationsCounter.countDown();
+ }
+ }
+ };
+
+ final Random randomGenerator = new Random();
+ for (int i = 0; i < testAllocationCount; i++) {
+ // schedule execution a little to in the future to simulate less predictable environment
+ testPooledExecutor.schedule(allocatePort, randomGenerator.nextInt(10), TimeUnit.MILLISECONDS);
+ }
+ allocationsCounter.await(10, TimeUnit.SECONDS);
+ }
+
+}
diff --git a/src/test/resources/example1/pom.xml b/src/test/resources/example1/pom.xml
index 79183ff1e8b2284e8f177f15c8ff006f8d27d01a..e8c9f0cecda60dd0406f0a9c30e52ffbeadd5f8f 100644
--- a/src/test/resources/example1/pom.xml
+++ b/src/test/resources/example1/pom.xml
@@ -29,7 +29,7 @@
<plugin>
<groupId>com.github.joelittlejohn.embedmongo</groupId>
<artifactId>embedmongo-maven-plugin</artifactId>
- <version>0.1.7-SNAPSHOT</version>
+ <version>0.1.8-SNAPSHOT</version>
<executions>
<execution>
<id>start</id>
diff --git a/src/test/resources/example2/pom.xml b/src/test/resources/example2/pom.xml
index cf9c60e491fca2d04964936d042234db952a79fe..4470b9a6eb3b321c3f7d72e9e7f7ce51cee706ac 100644
--- a/src/test/resources/example2/pom.xml
+++ b/src/test/resources/example2/pom.xml
@@ -29,7 +29,7 @@
<plugin>
<groupId>com.github.joelittlejohn.embedmongo</groupId>
<artifactId>embedmongo-maven-plugin</artifactId>
- <version>0.1.7-SNAPSHOT</version>
+ <version>0.1.8-SNAPSHOT</version>
<executions>
<execution>
<id>start</id>
diff --git a/src/test/resources/example3/pom.xml b/src/test/resources/example3/pom.xml
index 37241641a46af90db6aa5f428680f91b5590a1fc..59618aa89223df49ad40a65424d98ba27692a089 100644
--- a/src/test/resources/example3/pom.xml
+++ b/src/test/resources/example3/pom.xml
@@ -29,7 +29,7 @@
<plugin>
<groupId>com.github.joelittlejohn.embedmongo</groupId>
<artifactId>embedmongo-maven-plugin</artifactId>
- <version>0.1.7-SNAPSHOT</version>
+ <version>0.1.8-SNAPSHOT</version>
<executions>
<execution>
<id>start</id>
diff --git a/src/test/resources/pom.xml b/src/test/resources/pom.xml
index a873da6929a9489cd1d64eb6dcf34252e65da99a..21182d8147dc407e03adf008d17b920327fb58ff 100644
--- a/src/test/resources/pom.xml
+++ b/src/test/resources/pom.xml
@@ -12,8 +12,9 @@
<modules>
<module>example1</module>
- <module>example2</module>
+ <module>example2</module>
<module>example3</module>
+ <module>randomport</module>
</modules>
</project>
diff --git a/src/test/resources/randomport/pom.xml b/src/test/resources/randomport/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..96762ac2e77477fa0fe042449747f2729f0d91ea
--- /dev/null
+++ b/src/test/resources/randomport/pom.xml
@@ -0,0 +1,86 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>com.github.joelittlejohn.embedmongo</groupId>
+ <artifactId>embedmongo-maven-plugin-test-parent</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ </parent>
+
+ <groupId>com.github.joelittlejohn.embedmongo</groupId>
+ <artifactId>embedmongo-maven-plugin-random-port-test</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <url>https://github.com/joelittlejohn/embedmongo-maven-plugin</url>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.4</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.github.joelittlejohn.embedmongo</groupId>
+ <artifactId>embedmongo-maven-plugin</artifactId>
+ <version>0.1.8-SNAPSHOT</version>
+ <executions>
+ <execution>
+ <id>start</id>
+ <goals>
+ <goal>start</goal>
+ </goals>
+ <configuration>
+ <port>37017</port>
+ <databaseDirectory>/tmp/mongotest</databaseDirectory>
+ <logging>console</logging>
+ <version>v2.2.0</version>
+ </configuration>
+ </execution>
+ <execution>
+ <id>stop</id>
+ <goals>
+ <goal>stop</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <!--Runs integration tests and verifies their results-->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>2.12.4</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit-dep</artifactId>
+ <version>4.10</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.github.joelittlejohn.embedmongo</groupId>
+ <artifactId>embedmongo-maven-plugin</artifactId>
+ <version>0.1.8-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/src/test/resources/randomport/src/test/java/com/github/joelittlejohn/embedmongo/MongoIT.java b/src/test/resources/randomport/src/test/java/com/github/joelittlejohn/embedmongo/MongoIT.java
new file mode 100644
index 0000000000000000000000000000000000000000..8fe6fcc5064cb2d54acca8deb7feffce4b4972c5
--- /dev/null
+++ b/src/test/resources/randomport/src/test/java/com/github/joelittlejohn/embedmongo/MongoIT.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2012 Joe Littlejohn
+ *
+ * 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 com.github.joelittlejohn.embedmongo;
+
+import com.github.joelittlejohn.embedmongo.port.PortHelper;
+import org.junit.After;
+import org.junit.Test;
+
+import java.net.Socket;
+
+public class MongoIT {
+
+ private Socket mongoSocket;
+
+ @Test
+ public void testConnectMongo() throws Exception {
+ mongoSocket = new Socket("127.0.0.1", PortHelper.getMongoPort("embedmongo-maven-plugin-random-port-test"));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mongoSocket != null) {
+ mongoSocket.close();
+ }
+ }
+}