]> source.dussan.org Git - jgit.git/commitdiff
Allow retrying connecting SshSession in case of an exception 76/28676/4
authorStefan Lay <stefan.lay@sap.com>
Wed, 18 Jun 2014 09:33:34 +0000 (11:33 +0200)
committerMatthias Sohn <matthias.sohn@sap.com>
Fri, 20 Jun 2014 09:48:53 +0000 (11:48 +0200)
Connecting to an SshSession may fail due to different reasons. Jsch for
example often throws an com.jcraft.jsch.JschException: verify: false.[1]
The issue is still not fixed in JSch 0.1.51.

In such a case it is worth retrying to connect. The number of connection
attempts can be configured using ssh_config parameter
"ConnectionAttempts" [2].

Don't retry if the user canceled authentication.

[1] http://sourceforge.net/p/jsch/bugs/58/
[2] http://linux.die.net/man/5/ssh_config

Bug: 437656
Change-Id: I6dd2a3786b7d3f15f5a46821d8edac987a57e381
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
org.eclipse.jgit/.settings/.api_filters
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java

index 5836db1e60d853d2bc11912792e76da9b6af7640..8ec39bf56da6199c1d9c17b03a7dd0f360118c22 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, 2014 Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -96,6 +96,7 @@ public class OpenSshConfigTest extends RepositoryTestCase {
                assertEquals("repo.or.cz", h.getHostName());
                assertEquals("jex_junit", h.getUser());
                assertEquals(22, h.getPort());
+               assertEquals(1, h.getConnectionAttempts());
                assertNull(h.getIdentityFile());
        }
 
@@ -249,4 +250,35 @@ public class OpenSshConfigTest extends RepositoryTestCase {
                assertNotNull(h);
                assertTrue(h.isBatchMode());
        }
+
+       @Test
+       public void testAlias_ConnectionAttemptsDefault() throws Exception {
+               final Host h = osc.lookup("orcz");
+               assertNotNull(h);
+               assertEquals(1, h.getConnectionAttempts());
+       }
+
+       @Test
+       public void testAlias_ConnectionAttempts() throws Exception {
+               config("Host orcz\n" + "\tConnectionAttempts 5\n");
+               final Host h = osc.lookup("orcz");
+               assertNotNull(h);
+               assertEquals(5, h.getConnectionAttempts());
+       }
+
+       @Test
+       public void testAlias_invalidConnectionAttempts() throws Exception {
+               config("Host orcz\n" + "\tConnectionAttempts -1\n");
+               final Host h = osc.lookup("orcz");
+               assertNotNull(h);
+               assertEquals(1, h.getConnectionAttempts());
+       }
+
+       @Test
+       public void testAlias_badConnectionAttempts() throws Exception {
+               config("Host orcz\n" + "\tConnectionAttempts xxx\n");
+               final Host h = osc.lookup("orcz");
+               assertNotNull(h);
+               assertEquals(1, h.getConnectionAttempts());
+       }
 }
index 87a931c8791ee66fd01371144fc9bcd7481d11b9..56c9ee68a02d9de44a9ef01262df98ec93091758 100644 (file)
@@ -7,6 +7,12 @@
                 <message_argument value="3.2.0"/>
             </message_arguments>
         </filter>
+        <filter comment="minor addition" id="924844039">
+            <message_arguments>
+                <message_argument value="3.4.0"/>
+                <message_argument value="3.4.0"/>
+            </message_arguments>
+        </filter>
     </resource>
     <resource path="src/org/eclipse/jgit/transport/TransportHttp.java" type="org.eclipse.jgit.transport.TransportHttp">
         <filter comment="Method is only used by implementers of TransportHttp's API, minor version are allowed to break implementer API according to OSGi semantic versioning (http://www.osgi.org/wiki/uploads/Links/SemanticVersioning.pdf)" id="338792546">
index fd5801e6a5cbbb1edeb84c836f43af91d5833a50..5b12a0c6888fe90fbed42f3079fbed55027510f6 100644 (file)
@@ -508,6 +508,7 @@ transportProtoHTTP=HTTP
 transportProtoLocal=Local Git Repository
 transportProtoSFTP=SFTP
 transportProtoSSH=SSH
+transportSSHRetryInterrupt=Interrupted while waiting for retry
 treeEntryAlreadyExists=Tree entry "{0}" already exists.
 treeFilterMarkerTooManyFilters=Too many markTreeFilters passed, maximum number is {0} (passed {1})
 treeIteratorDoesNotSupportRemove=TreeIterator does not support remove()
index 8acfb54b85c690313092151be65a39b78abe007c..f075db3e57c4aede402f4c1325c7b7d322c76db4 100644 (file)
@@ -570,6 +570,7 @@ public class JGitText extends TranslationBundle {
        /***/ public String transportProtoLocal;
        /***/ public String transportProtoSFTP;
        /***/ public String transportProtoSSH;
+       /***/ public String transportSSHRetryInterrupt;
        /***/ public String treeEntryAlreadyExists;
        /***/ public String treeFilterMarkerTooManyFilters;
        /***/ public String treeIteratorDoesNotSupportRemove;
index 65e9d4dc5506ddee73692e73c51d76dffb4f3050..308741e9350ba61e8c99a9add567f035add51a3e 100644 (file)
@@ -110,7 +110,7 @@ public abstract class JschConfigSessionFactory extends SshSessionFactory {
                                        pass, host, port, hc);
 
                        int retries = 0;
-                       while (!session.isConnected() && retries < 3) {
+                       while (!session.isConnected()) {
                                try {
                                        retries++;
                                        session.connect(tms);
@@ -120,16 +120,30 @@ public abstract class JschConfigSessionFactory extends SshSessionFactory {
                                        // Make sure our known_hosts is not outdated
                                        knownHosts(getJSch(hc, fs), fs);
 
-                                       // if authentication failed maybe credentials changed at the
-                                       // remote end therefore reset credentials and retry
-                                       if (credentialsProvider != null && e.getCause() == null
-                                                       && e.getMessage().equals("Auth fail") //$NON-NLS-1$
-                                                       && retries < 3) {
-                                               credentialsProvider.reset(uri);
-                                               session = createSession(credentialsProvider, fs, user,
-                                                               pass, host, port, hc);
-                                       } else {
+                                       if (isAuthenticationCanceled(e)) {
+                                               throw e;
+                                       } else if (isAuthenticationFailed(e)
+                                                       && credentialsProvider != null) {
+                                               // if authentication failed maybe credentials changed at
+                                               // the remote end therefore reset credentials and retry
+                                               if (retries < 3) {
+                                                       credentialsProvider.reset(uri);
+                                                       session = createSession(credentialsProvider, fs,
+                                                                       user, pass, host, port, hc);
+                                               } else
+                                                       throw e;
+                                       } else if (retries >= hc.getConnectionAttempts()) {
                                                throw e;
+                                       } else {
+                                               try {
+                                                       Thread.sleep(1000);
+                                                       session = createSession(credentialsProvider, fs,
+                                                                       user, pass, host, port, hc);
+                                               } catch (InterruptedException e1) {
+                                                       throw new TransportException(
+                                                                       JGitText.get().transportSSHRetryInterrupt,
+                                                                       e1);
+                                               }
                                        }
                                }
                        }
@@ -147,6 +161,14 @@ public abstract class JschConfigSessionFactory extends SshSessionFactory {
 
        }
 
+       private static boolean isAuthenticationFailed(JSchException e) {
+               return e.getCause() == null && e.getMessage().equals("Auth fail"); //$NON-NLS-1$
+       }
+
+       private static boolean isAuthenticationCanceled(JSchException e) {
+               return e.getCause() == null && e.getMessage().equals("Auth cancel"); //$NON-NLS-1$
+       }
+
        private Session createSession(CredentialsProvider credentialsProvider,
                        FS fs, String user, final String pass, String host, int port,
                        final OpenSshConfig.Host hc) throws JSchException {
index 01716da70ae98eab6bd0383f0fce692c47ae598a..38f3a2ac77cacb491819cc13b6376433fb6d410f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008, 2014, Google Inc.
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -148,6 +148,8 @@ public class OpenSshConfig {
                        h.user = OpenSshConfig.userName();
                if (h.port == 0)
                        h.port = OpenSshConfig.SSH_PORT;
+               if (h.connectionAttempts == 0)
+                       h.connectionAttempts = 1;
                h.patternsApplied = true;
                return h;
        }
@@ -244,6 +246,18 @@ public class OpenSshConfig {
                                for (final Host c : current)
                                        if (c.strictHostKeyChecking == null)
                                                c.strictHostKeyChecking = value;
+                       } else if (StringUtils.equalsIgnoreCase(
+                                       "ConnectionAttempts", keyword)) { //$NON-NLS-1$
+                               try {
+                                       final int connectionAttempts = Integer.parseInt(dequote(argValue));
+                                       if (connectionAttempts > 0) {
+                                               for (final Host c : current)
+                                                       if (c.connectionAttempts == 0)
+                                                               c.connectionAttempts = connectionAttempts;
+                                       }
+                               } catch (NumberFormatException nfe) {
+                                       // ignore bad values
+                               }
                        }
                }
 
@@ -331,6 +345,8 @@ public class OpenSshConfig {
 
                String strictHostKeyChecking;
 
+               int connectionAttempts;
+
                void copyFrom(final Host src) {
                        if (hostName == null)
                                hostName = src.hostName;
@@ -346,6 +362,8 @@ public class OpenSshConfig {
                                batchMode = src.batchMode;
                        if (strictHostKeyChecking == null)
                                strictHostKeyChecking = src.strictHostKeyChecking;
+                       if (connectionAttempts == 0)
+                               connectionAttempts = src.connectionAttempts;
                }
 
                /**
@@ -402,5 +420,16 @@ public class OpenSshConfig {
                public boolean isBatchMode() {
                        return batchMode != null && batchMode.booleanValue();
                }
+
+               /**
+                * @return the number of tries (one per second) to connect before
+                *         exiting. The argument must be an integer. This may be useful
+                *         in scripts if the connection sometimes fails. The default is
+                *         1.
+                * @since 3.4
+                */
+               public int getConnectionAttempts() {
+                       return connectionAttempts;
+               }
        }
 }