From 4aafd4362caf198bdea97d2fdf2957aa5c345465 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Tue, 27 Sep 2011 08:22:26 -0400
Subject: [PATCH] Poke test during proposal phase.

---
 src/com/gitblit/FederationServlet.java             |   31 ++++++++-
 src/com/gitblit/GitBlit.java                       |    3 
 tests/com/gitblit/tests/FederationTests.java       |   10 ++-
 src/com/gitblit/wicket/pages/SendProposalPage.java |   38 +++++++++++-
 src/com/gitblit/Constants.java                     |   14 ++++
 src/com/gitblit/utils/FederationUtils.java         |   48 ++++++++++++++-
 6 files changed, 124 insertions(+), 20 deletions(-)

diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index b52f055..befa5fe 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -115,7 +115,7 @@
 	 * Enumeration representing the types of federation requests.
 	 */
 	public static enum FederationRequest {
-		PROPOSAL, PULL_REPOSITORIES, PULL_USERS, PULL_SETTINGS, STATUS;
+		POKE, PROPOSAL, PULL_REPOSITORIES, PULL_USERS, PULL_SETTINGS, STATUS;
 
 		public static FederationRequest fromName(String name) {
 			for (FederationRequest type : values()) {
@@ -181,4 +181,16 @@
 		}
 	}
 
+	/**
+	 * Enumeration representing the possible results of federation proposal
+	 * requests.
+	 */
+	public static enum FederationProposalResult {
+		ERROR, FEDERATION_DISABLED, MISSING_DATA, NO_PROPOSALS, NO_POKE, ACCEPTED;
+
+		@Override
+		public String toString() {
+			return name();
+		}
+	}
 }
diff --git a/src/com/gitblit/FederationServlet.java b/src/com/gitblit/FederationServlet.java
index 784ec33..7dc5d6a 100644
--- a/src/com/gitblit/FederationServlet.java
+++ b/src/com/gitblit/FederationServlet.java
@@ -35,6 +35,7 @@
 import com.gitblit.models.FederationProposal;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
+import com.gitblit.utils.FederationUtils;
 import com.gitblit.utils.HttpUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
@@ -110,6 +111,16 @@
 	private void processRequest(javax.servlet.http.HttpServletRequest request,
 			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
 			java.io.IOException {
+		FederationRequest reqType = FederationRequest.fromName(request.getParameter("req"));
+		logger.info(MessageFormat.format("Federation {0} request from {1}", reqType,
+				request.getRemoteAddr()));
+
+		if (FederationRequest.POKE.equals(reqType)) {
+			// Gitblit always responds to POKE requests to verify a connection
+			logger.info("Received federation POKE from " + request.getRemoteAddr());
+			return;
+		}
+
 		if (!GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {
 			logger.warn(Keys.git.enableGitServlet + " must be set TRUE for federation requests.");
 			response.sendError(HttpServletResponse.SC_FORBIDDEN);
@@ -123,11 +134,6 @@
 			response.sendError(HttpServletResponse.SC_FORBIDDEN);
 			return;
 		}
-
-		String token = request.getParameter("token");
-		FederationRequest reqType = FederationRequest.fromName(request.getParameter("req"));
-		logger.info(MessageFormat.format("Federation {0} request from {1}", reqType,
-				request.getRemoteAddr()));
 
 		if (FederationRequest.PROPOSAL.equals(reqType)) {
 			// Receive a gitblit federation proposal
@@ -156,6 +162,20 @@
 				logger.error(MessageFormat.format("Rejected {0} federation proposal from {1}",
 						proposal.tokenType.name(), proposal.url));
 				response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+				return;
+			}
+
+			// poke the origin Gitblit instance that is proposing federation
+			boolean poked = false;
+			try {
+				poked = FederationUtils.poke(proposal.url);
+			} catch (Exception e) {
+				logger.error("Failed to poke origin", e);
+			}
+			if (!poked) {
+				logger.error(MessageFormat.format("Failed to send federation poke to {0}",
+						proposal.url));
+				response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
 				return;
 			}
 
@@ -207,6 +227,7 @@
 		}
 
 		// Determine the federation tokens for this gitblit instance
+		String token = request.getParameter("token");
 		List<String> tokens = GitBlit.self().getFederationTokens();
 		if (!tokens.contains(token)) {
 			logger.warn(MessageFormat.format(
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 62b93e7..f7c43a8 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -971,7 +971,8 @@
 	 * @param proposal
 	 *            the proposal
 	 * @param gitblitUrl
-	 *            the url of your gitblit instance
+	 *            the url of your gitblit instance to send an email to
+	 *            administrators
 	 * @return true if the proposal was submitted
 	 */
 	public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
diff --git a/src/com/gitblit/utils/FederationUtils.java b/src/com/gitblit/utils/FederationUtils.java
index 9fd8817..fde9557 100644
--- a/src/com/gitblit/utils/FederationUtils.java
+++ b/src/com/gitblit/utils/FederationUtils.java
@@ -45,6 +45,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.gitblit.Constants.FederationProposalResult;
 import com.gitblit.Constants.FederationRequest;
 import com.gitblit.FederationServlet;
 import com.gitblit.IStoredSettings;
@@ -82,7 +83,7 @@
 	private static final SSLContext SSL_CONTEXT;
 
 	private static final DummyHostnameVerifier HOSTNAME_VERIFIER;
-	
+
 	private static final Logger LOGGER = LoggerFactory.getLogger(FederationUtils.class);
 
 	static {
@@ -182,21 +183,60 @@
 	}
 
 	/**
+	 * Sends a federation poke to the Gitblit instance at remoteUrl. Pokes are
+	 * sent by an pulling Gitblit instance to an origin Gitblit instance as part
+	 * of the proposal process. This is to ensure that the pulling Gitblit
+	 * instance has an IP route to the origin instance.
+	 * 
+	 * @param remoteUrl
+	 *            the remote Gitblit instance to send a federation proposal to
+	 * @param proposal
+	 *            a complete federation proposal
+	 * @return true if there is a route to the remoteUrl
+	 */
+	public static boolean poke(String remoteUrl) throws Exception {
+		String url = FederationServlet.asFederationLink(remoteUrl, null, FederationRequest.POKE);
+		Gson gson = new Gson();
+		String json = gson.toJson("POKE");
+		int status = writeJson(url, json);
+		return status == HttpServletResponse.SC_OK;
+	}
+
+	/**
 	 * Sends a federation proposal to the Gitblit instance at remoteUrl
 	 * 
 	 * @param remoteUrl
 	 *            the remote Gitblit instance to send a federation proposal to
 	 * @param proposal
 	 *            a complete federation proposal
-	 * @return true if the proposal was received
+	 * @return the federation proposal result code
 	 */
-	public static boolean propose(String remoteUrl, FederationProposal proposal) throws Exception {
+	public static FederationProposalResult propose(String remoteUrl, FederationProposal proposal)
+			throws Exception {
 		String url = FederationServlet
 				.asFederationLink(remoteUrl, null, FederationRequest.PROPOSAL);
 		Gson gson = new GsonBuilder().setPrettyPrinting().create();
 		String json = gson.toJson(proposal);
 		int status = writeJson(url, json);
-		return status == HttpServletResponse.SC_OK;
+		switch (status) {
+		case HttpServletResponse.SC_FORBIDDEN:
+			// remote Gitblit Federation disabled
+			return FederationProposalResult.FEDERATION_DISABLED;
+		case HttpServletResponse.SC_BAD_REQUEST:
+			// remote Gitblit did not receive any JSON data
+			return FederationProposalResult.MISSING_DATA;
+		case HttpServletResponse.SC_METHOD_NOT_ALLOWED:
+			// remote Gitblit not accepting proposals
+			return FederationProposalResult.NO_PROPOSALS;
+		case HttpServletResponse.SC_NOT_ACCEPTABLE:
+			// remote Gitblit failed to poke this Gitblit instance
+			return FederationProposalResult.NO_POKE;
+		case HttpServletResponse.SC_OK:
+			// received
+			return FederationProposalResult.ACCEPTED;
+		default:
+			return FederationProposalResult.ERROR;
+		}
 	}
 
 	/**
diff --git a/src/com/gitblit/wicket/pages/SendProposalPage.java b/src/com/gitblit/wicket/pages/SendProposalPage.java
index ea91f1b..635b432 100644
--- a/src/com/gitblit/wicket/pages/SendProposalPage.java
+++ b/src/com/gitblit/wicket/pages/SendProposalPage.java
@@ -26,6 +26,7 @@
 import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.model.CompoundPropertyModel;
 
+import com.gitblit.Constants.FederationProposalResult;
 import com.gitblit.GitBlit;
 import com.gitblit.models.FederationProposal;
 import com.gitblit.models.RepositoryModel;
@@ -78,17 +79,44 @@
 					error("Please enter a destination url for your proposal!");
 					return;
 				}
-				
+
 				// build new proposal
 				FederationProposal proposal = GitBlit.self().createFederationProposal(myUrl, token);
 				proposal.url = myUrl;
 				proposal.message = message;
 				try {
-					if (FederationUtils.propose(destinationUrl, proposal)) {
-						info(MessageFormat.format("Proposal successfully received by {0}.", destinationUrl));
+					FederationProposalResult res = FederationUtils
+							.propose(destinationUrl, proposal);
+					switch (res) {
+					case ACCEPTED:
+						info(MessageFormat.format("Proposal successfully received by {0}.",
+								destinationUrl));
 						setResponsePage(RepositoriesPage.class);
-					} else {
-						error(MessageFormat.format("Sorry, {0} rejected your proposal.", destinationUrl));
+						break;
+					case NO_POKE:
+						error(MessageFormat.format(
+								"Sorry, {0} could not find a Gitblit instance at {1}.",
+								destinationUrl, myUrl));
+						break;
+					case NO_PROPOSALS:
+						error(MessageFormat.format(
+								"Sorry, {0} is not accepting proposals at this time.",
+								destinationUrl));
+						break;
+					case FEDERATION_DISABLED:
+						error(MessageFormat
+								.format("Sorry, {0} is not configured to federate with any Gitblit instances.",
+										destinationUrl));
+						break;
+					case MISSING_DATA:
+						error(MessageFormat.format("Sorry, {0} did not receive any proposal data!",
+								destinationUrl));
+						break;
+					case ERROR:
+						error(MessageFormat.format(
+								"Sorry, {0} reports that an unexpected error occurred!",
+								destinationUrl));
+						break;
 					}
 				} catch (Exception e) {
 					if (!StringUtils.isEmpty(e.getMessage())) {
diff --git a/tests/com/gitblit/tests/FederationTests.java b/tests/com/gitblit/tests/FederationTests.java
index 28b089f..c5f7e8d 100644
--- a/tests/com/gitblit/tests/FederationTests.java
+++ b/tests/com/gitblit/tests/FederationTests.java
@@ -24,6 +24,7 @@
 import junit.framework.TestCase;
 
 import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.FederationProposalResult;
 import com.gitblit.Constants.FederationRequest;
 import com.gitblit.Constants.FederationToken;
 import com.gitblit.FederationServlet;
@@ -91,14 +92,15 @@
 				"testtoken", repositories);
 
 		// propose federation
-		assertTrue("proposal refused",
-				FederationUtils.propose("http://localhost:" + port, proposal));
+		assertEquals("proposal refused",
+				FederationUtils.propose("http://localhost:" + port, proposal),
+				FederationProposalResult.NO_PROPOSALS);
 	}
 
 	public void testPullRepositories() throws Exception {
 		try {
-			String url = FederationServlet.asFederationLink("http://localhost:" + port, "testtoken",
-					FederationRequest.PULL_REPOSITORIES);
+			String url = FederationServlet.asFederationLink("http://localhost:" + port,
+					"testtoken", FederationRequest.PULL_REPOSITORIES);
 			String json = FederationUtils.readJson(url);
 		} catch (IOException e) {
 			if (!e.getMessage().contains("403")) {

--
Gitblit v1.9.1