Skip to content

Commit

Permalink
Merged #91: [FIXED JENKINS-23571] Add configuration option for cache …
Browse files Browse the repository at this point in the history
…directory location
  • Loading branch information
jglick committed Jan 13, 2017
2 parents d092eb8 + 4a03ff9 commit 61d78a6
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 21 deletions.
39 changes: 28 additions & 11 deletions src/main/java/hudson/plugins/mercurial/Cache.java
Expand Up @@ -10,6 +10,7 @@
import hudson.model.TaskListener;
import hudson.util.ArgumentListBuilder;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
Expand Down Expand Up @@ -37,6 +38,8 @@ class Cache {
*/
private final String remote;

private final @CheckForNull String masterCacheRoot;

private final StandardUsernameCredentials credentials;

/**
Expand All @@ -50,19 +53,20 @@ class Cache {
private final ReentrantLock masterLock = new ReentrantLock(true);
private final Map<String, ReentrantLock> slaveNodesLocksMap = new HashMap<String, ReentrantLock>();

private Cache(String remote, String hash, StandardUsernameCredentials credentials) {
private Cache(String remote, String hash, StandardUsernameCredentials credentials, String masterCacheRoot) {
this.remote = remote;
this.hash = hash;
this.credentials = credentials;
this.masterCacheRoot = masterCacheRoot;
}

private static final Map<String, Cache> CACHES = new HashMap<String, Cache>();

public synchronized static @NonNull Cache fromURL(String remote, StandardUsernameCredentials credentials) {
String h = hashSource(remote, credentials);
public synchronized static @NonNull Cache fromURL(String remote, StandardUsernameCredentials credentials, @CheckForNull String masterCacheRoot) {
String h = hashSource(remote, credentials, masterCacheRoot);
Cache cache = CACHES.get(h);
if (cache == null) {
CACHES.put(h, cache = new Cache(remote, h, credentials));
CACHES.put(h, cache = new Cache(remote, h, credentials, masterCacheRoot));
}
return cache;
}
Expand Down Expand Up @@ -104,12 +108,18 @@ private synchronized ReentrantLock getLockForSlaveNode(String node) {
if (master == null) { // Should not happen
throw new IOException("Cannot retrieve the Jenkins master node");
}
final FilePath rootPath = master.getRootPath();
if (rootPath == null) {
throw new IOException("Cannot retrieve the root directory of the Jenkins master node");

FilePath masterCaches = null;
if (masterCacheRoot != null){
masterCaches = new FilePath(master.getChannel(), masterCacheRoot);
} else {
FilePath rootPath = master.getRootPath();
if (rootPath == null) {
throw new IOException("Cannot retrieve the root directory of the Jenkins master node");
}
masterCaches = rootPath.child("hgcache");
}

FilePath masterCaches = rootPath.child("hgcache");

FilePath masterCache = masterCaches.child(hash);
Launcher masterLauncher = node == master ? launcher : master.createLauncher(listener);

Expand Down Expand Up @@ -241,12 +251,19 @@ private synchronized ReentrantLock getLockForSlaveNode(String node) {
/**
* Hash a URL into a string that only contains characters that are safe as directory names.
*/
static String hashSource(String source, StandardUsernameCredentials credentials) {
static String hashSource(String source, StandardUsernameCredentials credentials, @CheckForNull String masterCacheRoot) {
if (!source.endsWith("/")) {
source += "/";
}
Matcher m = Pattern.compile(".+[/]([^/:]+)(:\\d+)?[/]?").matcher(source);
String digestible = credentials == null ? source : source + '#' + credentials.getId();
String digestible = source;
if (credentials != null){
digestible += '#' + credentials.getId();
}
if (masterCacheRoot != null){
digestible += "#" + masterCacheRoot.replaceAll(File.pathSeparator, "_");
}

BigInteger hash;
try {
hash = new BigInteger(1, MessageDigest.getInstance("SHA-1").digest(digestible.getBytes("UTF-8")));
Expand Down
21 changes: 19 additions & 2 deletions src/main/java/hudson/plugins/mercurial/MercurialInstallation.java
Expand Up @@ -64,21 +64,34 @@ public class MercurialInstallation extends ToolInstallation implements
private String executable;
private boolean debug;
private boolean useCaches;
private final String masterCacheRoot;
private boolean useSharing;
private final String config;

/** for backwards compatibility */
@Deprecated
public MercurialInstallation(String name, String home, String executable,
boolean debug, boolean useCaches,
boolean useSharing, List<? extends ToolProperty<?>> properties) {
this(name, home, executable, debug, useCaches, useSharing, null, properties);
this(name, home, executable, debug, useCaches, null, useSharing, null, properties);
}

@DataBoundConstructor public MercurialInstallation(String name, String home, String executable, boolean debug, boolean useCaches, boolean useSharing, String config, List<? extends ToolProperty<?>> properties) {
/** for backwards compatibility */
@Deprecated
public MercurialInstallation(String name, String home, String executable,
boolean debug, boolean useCaches,
boolean useSharing, String config, List<? extends ToolProperty<?>> properties) {
this(name, home, executable, debug, useCaches, null, useSharing, config, properties);
}

@DataBoundConstructor public MercurialInstallation(String name, String home, String executable,
boolean debug, boolean useCaches, String masterCacheRoot,
boolean useSharing, String config, List<? extends ToolProperty<?>> properties) {
super(name, home, properties);
this.executable = Util.fixEmpty(executable);
this.debug = debug;
this.useCaches = useCaches || useSharing;
this.masterCacheRoot = Util.fixEmptyAndTrim(masterCacheRoot);
this.config = Util.fixEmptyAndTrim(config);
this.useSharing = useSharing;
}
Expand All @@ -99,6 +112,10 @@ public boolean isUseCaches() {
return useCaches;
}

public String getMasterCacheRoot() {
return masterCacheRoot;
}

public boolean isUseSharing() {
return useSharing;
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/hudson/plugins/mercurial/MercurialSCM.java
Expand Up @@ -960,7 +960,7 @@ private boolean causedByMissingHg(IOException e) {
return null;
}
try {
FilePath cache = Cache.fromURL(getSource(env), credentials).repositoryCache(inst, node, launcher, listener, useTimeout);
FilePath cache = Cache.fromURL(getSource(env), credentials, inst.getMasterCacheRoot()).repositoryCache(inst, node, launcher, listener, useTimeout);
if (cache != null) {
return new CachedRepo(cache.getRemote(), inst.isUseSharing());
} else {
Expand Down
Expand Up @@ -126,7 +126,7 @@ protected void retrieve(@edu.umd.cs.findbugs.annotations.CheckForNull SCMSourceC
}
Launcher launcher = node.createLauncher(listener);
StandardUsernameCredentials credentials = getCredentials();
final FilePath cache = Cache.fromURL(source, credentials).repositoryCache(inst, node, launcher, listener, true);
final FilePath cache = Cache.fromURL(source, credentials, inst.getMasterCacheRoot()).repositoryCache(inst, node, launcher, listener, true);
if (cache == null) {
listener.error("Could not use caches, not fetching branch heads");
return;
Expand Down
Expand Up @@ -12,6 +12,9 @@
<f:entry field="useCaches" title="${%Use Repository Caches}">
<f:checkbox/>
</f:entry>
<f:entry field="masterCacheRoot" title="${%Master cache directory}">
<f:textbox/>
</f:entry>
<f:entry field="useSharing" title="${%Use Repository Sharing}">
<f:checkbox/>
</f:entry>
Expand Down
@@ -0,0 +1,4 @@
<div>
Location of cached repositories on the master node.
Default : $JENKINS_HOME/hgcache
</div>
14 changes: 8 additions & 6 deletions src/test/java/hudson/plugins/mercurial/CacheTest.java
Expand Up @@ -11,17 +11,19 @@
public class CacheTest {

@Test public void hashSource() throws Exception {
assertEquals("5439A9B4063BB8F4885037E71B5079E1913DB6CA-core-main", Cache.hashSource("http://hg.netbeans.org/core-main/", null));
assertEquals("5439A9B4063BB8F4885037E71B5079E1913DB6CA-core-main", Cache.hashSource("http://hg.netbeans.org/core-main", null));
assertEquals("5731708C5EEAF9F1320B57D5F6A21E85EA5ADF2D-project", Cache.hashSource("ssh://dude@math.utexas.edu/some/project/", null));
assertEquals("210ED9E2610F74A473985D8D9EF4483D5D30265E-project", Cache.hashSource("ssh://dudette@math.utexas.edu/some/project/", null));
assertEquals("5439A9B4063BB8F4885037E71B5079E1913DB6CA-core-main", Cache.hashSource("http://hg.netbeans.org/core-main/", null, null));
assertEquals("5439A9B4063BB8F4885037E71B5079E1913DB6CA-core-main", Cache.hashSource("http://hg.netbeans.org/core-main", null, null));
assertEquals("5731708C5EEAF9F1320B57D5F6A21E85EA5ADF2D-project", Cache.hashSource("ssh://dude@math.utexas.edu/some/project/", null, null));
assertEquals("210ED9E2610F74A473985D8D9EF4483D5D30265E-project", Cache.hashSource("ssh://dudette@math.utexas.edu/some/project/", null, null));
assertEquals("D3D58986EB0F726F38EE6393B1DB943C0BAD0B4D-project", Cache.hashSource("ssh://dudette@math.utexas.edu/some/project/", null, "/var/tmp/hgcache"));
// Cannot use UsernamePasswordCredentialsImpl from a unit test, since it tries to actually decrypt the password, which requires Jenkins.instance.
assertEquals("0D1FD823FDA2F7144C463007FEAF9F824333B3D2-core-main-bob-at-nowhere.net", Cache.hashSource("http://hg.netbeans.org/core-main/", new MockUsernamePasswordCredentials(CredentialsScope.GLOBAL, "what-ever", "bob@nowhere.net")));
assertEquals("0D1FD823FDA2F7144C463007FEAF9F824333B3D2-core-main-bob-at-nowhere.net",
Cache.hashSource("http://hg.netbeans.org/core-main/", new MockUsernamePasswordCredentials(CredentialsScope.GLOBAL, "what-ever", "bob@nowhere.net"),null));
}

@Bug(12544)
@Test public void hashSource2() throws Exception {
assertEquals("DA7E6A4632009859A61A551999EE2109EBB69267-ronaldradial", Cache.hashSource("http://ronaldradial:8000/", null));
assertEquals("DA7E6A4632009859A61A551999EE2109EBB69267-ronaldradial", Cache.hashSource("http://ronaldradial:8000/", null,null));
}

private static class MockUsernamePasswordCredentials extends BaseStandardCredentials implements StandardUsernamePasswordCredentials {
Expand Down
@@ -0,0 +1,52 @@
package hudson.plugins.mercurial;

import java.io.File;
import java.util.Collections;
import java.util.regex.Pattern;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.JenkinsRule;

import hudson.model.FreeStyleProject;
import hudson.tools.ToolProperty;

public class CachingCustomDirSCMTest {

@Rule
public JenkinsRule j = new JenkinsRule();
@Rule
public MercurialRule m = new MercurialRule(j);
@Rule
public TemporaryFolder tmp = new TemporaryFolder();
private File repo;

private static final String CACHING_INSTALLATION = "caching-custom-dir";

@Before
public void setUp() throws Exception {
repo = tmp.getRoot();
j.jenkins
.getDescriptorByType(MercurialInstallation.DescriptorImpl.class)
.setInstallations(new MercurialInstallation(CACHING_INSTALLATION, "", "hg",
false, true, new File(tmp.newFolder(),"custom-cache-dir").getAbsolutePath().toString(), false, "",
Collections.<ToolProperty<?>> emptyList()));
}

@Test
public void customCacheLocationFromSlave() throws Exception {
FreeStyleProject p = j.createFreeStyleProject();
p.setScm(new MercurialSCM(CACHING_INSTALLATION, repo.getPath(), null, null,
null, null, false));
p.setAssignedNode(j.createOnlineSlave());
m.hg(repo, "init");
m.touchAndCommit(repo, "a");
String log = m.buildAndCheck(p, "a");
Pattern pattern = Pattern.compile("hg clone .*custom-cache-dir");
Assert.assertTrue(pattern.matcher(log).find());
}

}

0 comments on commit 61d78a6

Please sign in to comment.