提交 5384602b 编写于 作者: W Wadeck Follonier

Merge branch 'master' into security-master

-Pconsume-incrementals
-Pmight-produce-incrementals
package hudson.cli;
import org.junit.Assert;
import org.junit.Test;
public class HexDumpTest {
@Test
public void testToHex1() {
Assert.assertEquals("'fooBar'",
HexDump.toHex(new byte[] {'f', 'o', 'o', 'B', 'a', 'r'}));
Assert.assertEquals("0xc3",
HexDump.toHex(new byte[] {(byte)'Ã'}));
Assert.assertEquals("0xac '100'",
HexDump.toHex(new byte[] {(byte)'€', '1', '0', '0'}));
Assert.assertEquals("'1' 0xf7 '2'",
HexDump.toHex(new byte[] {'1', (byte)'÷', '2'}));
Assert.assertEquals("'foo' 0x0a\n'Bar'",
HexDump.toHex(new byte[] {'f', 'o', 'o', '\n', 'B', 'a', 'r'}));
}
@Test
public void testToHex2() {
Assert.assertEquals("'ooBa'",
HexDump.toHex(new byte[] {'f', 'o', 'o', 'B', 'a', 'r'}, 1, 4));
Assert.assertEquals("0xc3",
HexDump.toHex(new byte[] {(byte)'Ã'}, 0, 1));
Assert.assertEquals("0xac '10'",
HexDump.toHex(new byte[] {(byte)'€', '1', '0', '0'}, 0, 3));
Assert.assertEquals("0xf7 '2'",
HexDump.toHex(new byte[] {'1', (byte)'÷', '2'}, 1, 2));
Assert.assertEquals("'Bar'",
HexDump.toHex(new byte[] {'f', 'o', 'o', '\n', 'B', 'a', 'r'}, 4, 3));
Assert.assertEquals("",
HexDump.toHex(new byte[] {'f', 'o', 'o', 'B', 'a', 'r'}, 0, 0));
}
}
......@@ -101,6 +101,11 @@ THE SOFTWARE.
</exclusions>
</dependency>
<dependency>
<groupId>org.connectbot.jbcrypt</groupId>
<artifactId>jbcrypt</artifactId>
<version>1.0.0</version>
</dependency>
<dependency> <!-- for compatibility only; all new code should use JNR -->
<groupId>org.jruby.ext.posix</groupId>
<artifactId>jna-posix</artifactId>
......@@ -116,11 +121,6 @@ THE SOFTWARE.
<artifactId>trilead-putty-extension</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci</groupId>
<artifactId>trilead-ssh2</artifactId>
<version>build-217-jenkins-14</version>
</dependency>
<dependency>
<groupId>org.kohsuke.stapler</groupId>
<artifactId>stapler-groovy</artifactId>
......
......@@ -64,6 +64,7 @@ import jenkins.install.InstallUtil;
import jenkins.model.Jenkins;
import jenkins.plugins.DetachedPluginsUtil;
import jenkins.security.CustomClassFilter;
import jenkins.telemetry.impl.java11.MissingClassTelemetry;
import jenkins.util.SystemProperties;
import jenkins.util.io.OnMaster;
import jenkins.util.xml.RestrictiveEntityResolver;
......@@ -601,7 +602,10 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
void considerDetachedPlugin(String shortName) {
if (new File(rootDir, shortName + ".jpi").isFile() || new File(rootDir, shortName + ".hpi").isFile()) {
if (new File(rootDir, shortName + ".jpi").isFile() ||
new File(rootDir, shortName + ".hpi").isFile() ||
new File(rootDir, shortName + ".jpl").isFile() ||
new File(rootDir, shortName + ".hpl").isFile()) {
LOGGER.fine(() -> "not considering loading a detached dependency " + shortName + " as it is already on disk");
return;
}
......@@ -2065,7 +2069,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
loaded.put(name, null);
}
// not found in any of the classloader. delegate.
throw new ClassNotFoundException(name);
ClassNotFoundException cnfe = new ClassNotFoundException(name);
MissingClassTelemetry.reportException(name, cnfe);
throw cnfe;
}
@Override
......
......@@ -84,6 +84,7 @@ import static hudson.PluginWrapper.PluginDisableStatus.ERROR_DISABLING;
import static hudson.PluginWrapper.PluginDisableStatus.NOT_DISABLED_DEPENDANTS;
import static hudson.PluginWrapper.PluginDisableStatus.NO_SUCH_PLUGIN;
import static java.util.logging.Level.WARNING;
import jenkins.plugins.DetachedPluginsUtil;
import static org.apache.commons.io.FilenameUtils.getBaseName;
/**
......@@ -999,6 +1000,15 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
return !archive.exists();
}
/**
* Same as {@link DetachedPluginsUtil#isDetachedPlugin}.
* @since TODO
*/
@Exported
public boolean isDetached() {
return DetachedPluginsUtil.isDetachedPlugin(shortName);
}
/**
* Sort by short name.
*/
......
......@@ -396,7 +396,7 @@ public final class ProxyConfiguration extends AbstractDescribableImpl<ProxyConfi
GetMethod method = null;
try {
method = new GetMethod(testUrl);
method.getParams().setParameter("http.socket.timeout", DEFAULT_CONNECT_TIMEOUT_MILLIS > 0 ? DEFAULT_CONNECT_TIMEOUT_MILLIS : TimeUnit.SECONDS.toMillis(30));
method.getParams().setParameter("http.socket.timeout", DEFAULT_CONNECT_TIMEOUT_MILLIS > 0 ? DEFAULT_CONNECT_TIMEOUT_MILLIS : (int)TimeUnit.SECONDS.toMillis(30));
HttpClient client = new HttpClient();
if (Util.fixEmptyAndTrim(name) != null && !isNoProxyHost(host, noProxyHost)) {
......
package hudson.init.impl;
import hudson.init.Initializer;
import java.io.EOFException;
import jenkins.model.Jenkins;
import jenkins.telemetry.impl.java11.MissingClassTelemetry;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.WebApp;
import org.kohsuke.stapler.compression.CompressionFilter;
import javax.servlet.ServletException;
import java.io.EOFException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.kohsuke.stapler.Stapler;
/**
* Deals with exceptions that get thrown all the way up to the Stapler rendering layer.
*/
......@@ -29,6 +29,10 @@ public class InstallUncaughtExceptionHandler {
}
req.setAttribute("javax.servlet.error.exception",e);
try {
// If we have an exception, let's see if it's related with missing classes on Java 11. We reach
// here with a ClassNotFoundException in an action, for example. Setting the report here is the only
// way to catch the missing classes when the plugin uses Thread.currentThread().getContextClassLoader().loadClass
MissingClassTelemetry.reportExceptionInside(e);
WebApp.get(j.servletContext).getSomeStapler().invoke(req, rsp, j, "/oops");
} catch (ServletException | IOException x) {
if (!Stapler.isSocketException(x)) {
......@@ -42,10 +46,10 @@ public class InstallUncaughtExceptionHandler {
}
catch (SecurityException ex) {
LOGGER.log(Level.SEVERE,
"Failed to set the default UncaughtExceptionHandler. " +
"Failed to set the default UncaughtExceptionHandler. " +
"If any threads die due to unhandled coding errors then there will be no logging of this information. " +
"The lack of this diagnostic information will make it harder to track down issues which will reduce the supportability of Jenkins. " +
"It is highly recommended that you consult the documentation that comes with you servlet container on how to allow the " +
"The lack of this diagnostic information will make it harder to track down issues which will reduce the supportability of Jenkins. " +
"It is highly recommended that you consult the documentation that comes with you servlet container on how to allow the " +
"`setDefaultUncaughtExceptionHandler` permission and enable it.", ex);
}
}
......@@ -70,10 +74,14 @@ public class InstallUncaughtExceptionHandler {
"A thread (" + t.getName() + '/' + t.getId()
+ ") died unexpectedly due to an uncaught exception, this may leave your Jenkins in a bad way and is usually indicative of a bug in the code.",
ex);
// If we have an exception, let's see if it's related with missing classes on Java 11. We reach
// here with a ClassNotFoundException in an action, for example. Setting the report here is the only
// way to catch the missing classes when the plugin uses Thread.currentThread().getContextClassLoader().loadClass
MissingClassTelemetry.reportExceptionInside(ex);
}
}
private InstallUncaughtExceptionHandler() {}
}
......@@ -27,6 +27,7 @@ import jenkins.model.PeepholePermalink;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.CheckForNull;
/**
* Optional interface for {@link Action}s that are attached
......@@ -86,7 +87,7 @@ public interface PermalinkProjectAction extends Action {
* @return null
* if the target of the permalink doesn't exist.
*/
public abstract Run<?,?> resolve(Job<?,?> job);
public abstract @CheckForNull Run<?,?> resolve(Job<?,?> job);
/**
* List of {@link Permalink}s that are built into Jenkins.
......
......@@ -107,7 +107,6 @@ import jenkins.model.ArtifactManagerFactory;
import jenkins.model.BuildDiscarder;
import jenkins.model.Jenkins;
import jenkins.model.JenkinsLocationConfiguration;
import jenkins.model.PeepholePermalink;
import jenkins.model.RunAction2;
import jenkins.model.StandardArtifactManager;
import jenkins.model.lazy.BuildReference;
......@@ -1813,8 +1812,6 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
RunListener.fireStarted(this,listener);
updateSymlinks(listener);
setResult(job.run(listener));
LOGGER.log(INFO, "{0} main build action completed: {1}", new Object[] {this, result});
......@@ -1926,36 +1923,10 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
}
/**
* Makes sure that {@code lastSuccessful} and {@code lastStable} legacy links in the project’s root directory exist.
* Normally you do not need to call this explicitly, since {@link #execute} does so,
* but this may be needed if you are creating synthetic {@link Run}s as part of a container project (such as Maven builds in a module set).
* You should also ensure that {@link RunListener#fireStarted} and {@link RunListener#fireCompleted} are called.
* @param listener probably unused
* @throws InterruptedException probably not thrown
* @since 1.530
* @deprecated After JENKINS-37862 this no longer does anything.
*/
public final void updateSymlinks(@Nonnull TaskListener listener) throws InterruptedException {
createSymlink(listener, "lastSuccessful", PermalinkProjectAction.Permalink.LAST_SUCCESSFUL_BUILD);
createSymlink(listener, "lastStable", PermalinkProjectAction.Permalink.LAST_STABLE_BUILD);
}
/**
* Backward compatibility.
*
* We used to have $JENKINS_HOME/jobs/JOBNAME/lastStable and lastSuccessful symlinked to the appropriate
* builds, but now those are done in {@link PeepholePermalink}. So here, we simply create symlinks that
* resolves to the symlink created by {@link PeepholePermalink}.
*/
private void createSymlink(@Nonnull TaskListener listener, @Nonnull String name, @Nonnull PermalinkProjectAction.Permalink target) throws InterruptedException {
File buildDir = getParent().getBuildDir();
File rootDir = getParent().getRootDir();
String targetDir;
if (buildDir.equals(new File(rootDir, "builds"))) {
targetDir = "builds" + File.separator + target.getId();
} else {
targetDir = buildDir + File.separator + target.getId();
}
Util.createSymlink(rootDir, targetDir, name, listener);
}
@Deprecated
public final void updateSymlinks(@Nonnull TaskListener listener) throws InterruptedException {}
/**
* Handles a fatal build problem (exception) that occurred during the build.
......
......@@ -159,6 +159,11 @@ public abstract class Slave extends Node implements Serializable {
*/
private String userId;
/**
* Use {@link #Slave(String, String, ComputerLauncher)} and set the rest through setters.
* @deprecated since FIXME
*/
@Deprecated
public Slave(String name, String nodeDescription, String remoteFS, String numExecutors,
Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy, List<? extends NodeProperty<?>> nodeProperties) throws FormException, IOException {
this(name,nodeDescription,remoteFS,Util.tryParseNumber(numExecutors, 1).intValue(),mode,labelString,launcher,retentionStrategy, nodeProperties);
......
......@@ -84,7 +84,13 @@ public class HttpSessionContextIntegrationFilter2 extends HttpSessionContextInte
return false;
}
User userFromSession = User.getById(authentication.getName(), false);
User userFromSession;
try {
userFromSession = User.getById(authentication.getName(), false);
} catch (IllegalStateException ise) {
logger.warn("Encountered IllegalStateException trying to get a user. System init may not have completed yet. Invalidating user session.");
return false;
}
if (userFromSession == null) {
// no requirement for further test as there is no user inside
return false;
......
......@@ -68,6 +68,7 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
......@@ -290,15 +291,50 @@ public abstract class SecurityRealm extends AbstractDescribableImpl<SecurityReal
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder.clearContext();
// reset remember-me cookie
Cookie cookie = new Cookie(ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,"");
String contextPath = req.getContextPath().length() > 0 ? req.getContextPath() : "/";
resetRememberMeCookie(req, rsp, contextPath);
clearStaleSessionCookies(req, rsp, contextPath);
rsp.sendRedirect2(getPostLogOutUrl(req,auth));
}
private void resetRememberMeCookie(StaplerRequest req, StaplerResponse rsp, String contextPath) {
Cookie cookie = new Cookie(ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, "");
cookie.setMaxAge(0);
cookie.setSecure(req.isSecure());
cookie.setHttpOnly(true);
cookie.setPath(req.getContextPath().length()>0 ? req.getContextPath() : "/");
cookie.setPath(contextPath);
rsp.addCookie(cookie);
}
rsp.sendRedirect2(getPostLogOutUrl(req,auth));
private void clearStaleSessionCookies(StaplerRequest req, StaplerResponse rsp, String contextPath) {
/* While "executableWar.jetty.sessionIdCookieName" and
* "executableWar.jetty.disableCustomSessionIdCookieName"
* <https://github.com/jenkinsci/extras-executable-war/blob/6558df699d1366b18d045d2ffda3e970df377873/src/main/java/Main.java#L79-L97>
* can influence the current running behavior of the generated session cookie, we aren't interested
* in either of them at all.
*
* What matters to us are any stale cookies.
* Those cookies would have been created by this jenkins in a different incarnation, when it
* could, perhaps, have had different configuration flags, including for those configurables.
*
* Thus, we unconditionally zap all JSESSIONID. cookies.
* a new cookie will be generated by sendRedirect2(...)
*
* We don't care about JSESSIONID cookies outside our path because it's the browser's
* responsibility not to send them to us in the first place.
*/
final String cookieName = "JSESSIONID.";
for (Cookie cookie : req.getCookies()) {
if (cookie.getName().startsWith(cookieName)) {
LOGGER.log(Level.FINE, "Removing cookie {0} during logout", cookie.getName());
// one reason users log out is to clear their session(s)
// so tell the browser to drop all old sessions
cookie.setMaxAge(0);
cookie.setValue("");
rsp.addCookie(cookie);
}
}
}
/**
......
......@@ -269,6 +269,14 @@ public class TokenBasedRememberMeServices2 extends TokenBasedRememberMeServices
userDetails.getAuthorities());
auth.setDetails(authenticationDetailsSource.buildDetails(request));
// Ensure this session is linked to the user's seed
if (!UserSeedProperty.DISABLE_USER_SEED) {
User user = User.get(auth);
UserSeedProperty userSeed = user.getProperty(UserSeedProperty.class);
String sessionSeed = userSeed.getSeed();
request.getSession().setAttribute(UserSeedProperty.USER_SESSION_SEED, sessionSeed);
}
return auth;
}
......
......@@ -30,6 +30,7 @@ import hudson.model.Slave;
import hudson.model.TaskListener;
import hudson.util.StreamTaskListener;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
......@@ -43,11 +44,33 @@ import java.util.logging.Logger;
* @since 1.382
*/
public abstract class AbstractCloudSlave extends Slave {
public AbstractCloudSlave(String name, String nodeDescription, String remoteFS, String numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy, List<? extends NodeProperty<?>> nodeProperties) throws FormException, IOException {
public AbstractCloudSlave(@Nonnull String name, String remoteFS, ComputerLauncher launcher)
throws FormException, IOException {
super(name, remoteFS, launcher);
}
/**
* Use {@link #AbstractCloudSlave(java.lang.String, java.lang.String, hudson.slaves.ComputerLauncher)}
* @deprecated since FIXME
*/
@Deprecated
public AbstractCloudSlave(String name, String nodeDescription, String remoteFS, String numExecutors,
Mode mode, String labelString, ComputerLauncher launcher,
RetentionStrategy retentionStrategy, List<? extends NodeProperty<?>> nodeProperties)
throws FormException, IOException {
super(name, nodeDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, nodeProperties);
}
public AbstractCloudSlave(String name, String nodeDescription, String remoteFS, int numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy, List<? extends NodeProperty<?>> nodeProperties) throws FormException, IOException {
/**
* Use {@link #AbstractCloudSlave(java.lang.String, java.lang.String, hudson.slaves.ComputerLauncher)}
* @deprecated since FIXME
*/
@Deprecated
public AbstractCloudSlave(String name, String nodeDescription, String remoteFS, int numExecutors,
Mode mode, String labelString, ComputerLauncher launcher,
RetentionStrategy retentionStrategy, List<? extends NodeProperty<?>> nodeProperties)
throws FormException, IOException {
super(name, nodeDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, nodeProperties);
}
......
......@@ -32,6 +32,7 @@ import static hudson.model.LoadStatistics.DECAY;
import hudson.model.MultiStageTimeSeries.TimeScale;
import hudson.Extension;
import jenkins.util.SystemProperties;
import jenkins.util.Timer;
import org.jenkinsci.Symbol;
import javax.annotation.Nonnull;
......@@ -136,6 +137,7 @@ public class NodeProvisioner {
private StrategyState provisioningState = null;
private transient volatile long lastSuggestedReview;
private transient volatile boolean queuedReview;
/**
* Exponential moving average of the "planned capacity" over time, which is the number of
......@@ -165,19 +167,28 @@ public class NodeProvisioner {
/**
* Give the {@link NodeProvisioner} a hint that now would be a good time to think about provisioning some nodes.
* The hint will be ignored if subjected to excessive pestering by callers.
* Hints are throttled to one every second.
*
* @since 1.415
*/
public void suggestReviewNow() {
if (System.currentTimeMillis() > lastSuggestedReview + TimeUnit.SECONDS.toMillis(1)) {
lastSuggestedReview = System.currentTimeMillis();
Computer.threadPoolForRemoting.submit(new Runnable() {
public void run() {
if (!queuedReview) {
long delay = TimeUnit.SECONDS.toMillis(1) - (System.currentTimeMillis() - lastSuggestedReview);
if (delay < 0) {
lastSuggestedReview = System.currentTimeMillis();
Computer.threadPoolForRemoting.submit(() -> {
LOGGER.fine(() -> "running suggested review for " + label);
update();
}
});
});
} else {
queuedReview = true;
LOGGER.fine(() -> "running suggested review in " + delay + " ms for " + label);
Timer.get().schedule(() -> {
lastSuggestedReview = System.currentTimeMillis();
LOGGER.fine(() -> "running suggested review for " + label + " after " + delay + " ms");
update();
}, delay, TimeUnit.MILLISECONDS);
}
} else {
LOGGER.fine(() -> "ignoring suggested review for " + label);
}
......@@ -195,7 +206,7 @@ public class NodeProvisioner {
provisioningLock.lock();
try {
lastSuggestedReview = System.currentTimeMillis();
queuedReview = false;
// We need to get the lock on Queue for two reasons:
// 1. We will potentially adding a lot of nodes and we don't want to fight with Queue#maintain to acquire
// the Queue#lock in order to add each node. Much better is to hold the Queue#lock until all nodes
......@@ -209,9 +220,7 @@ public class NodeProvisioner {
// that causes issues in Queue#maintain) we should be able to remove the need for Queue#lock
//
// TODO once Nodes#addNode is made lock free, we should be able to remove the requirement for Queue#lock
Queue.withLock(new Runnable() {
@Override
public void run() {
Queue.withLock(() -> {
Jenkins jenkins = Jenkins.get();
// clean up the cancelled launch activity, then count the # of executors that we are about to
// bring up.
......@@ -311,7 +320,6 @@ public class NodeProvisioner {
} else {
provisioningState = new StrategyState(snapshot, label, plannedCapacitySnapshot);
}
}
});
if (provisioningState != null) {
......
......@@ -44,6 +44,8 @@ import jenkins.security.stapler.StaplerFilteredActionListener;
import jenkins.security.stapler.StaplerDispatchable;
import jenkins.security.RedactSecretJsonInErrorMessageSanitizer;
import jenkins.security.stapler.TypedFilter;
import jenkins.telemetry.impl.java11.CatcherClassLoader;
import jenkins.telemetry.impl.java11.MissingClassTelemetry;
import jenkins.util.SystemProperties;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
......@@ -871,6 +873,12 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
// doing this early allows InitStrategy to set environment upfront
//Telemetry: add interceptor classloader
//These lines allow the catcher to be present on Thread.currentThread().getContextClassLoader() in every plugin which
//allow us to detect failures in every plugin loading classes by this way.
if (MissingClassTelemetry.enabled() && !(Thread.currentThread().getContextClassLoader() instanceof CatcherClassLoader)) {
Thread.currentThread().setContextClassLoader(new CatcherClassLoader(Thread.currentThread().getContextClassLoader()));
}
final InitStrategy is = InitStrategy.get(Thread.currentThread().getContextClassLoader());
Trigger.timer = new java.util.Timer("Jenkins cron thread");
......@@ -911,8 +919,20 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
pluginManager = PluginManager.createDefault(this);
this.pluginManager = pluginManager;
WebApp webApp = WebApp.get(servletContext);
//Telemetry: add interceptor classloader
//These lines allows the catcher to be present on Thread.currentThread().getContextClassLoader() in every plugin which
//allow us to detect failures in every plugin loading classes by this way.
// JSON binding needs to be able to see all the classes from all the plugins
webApp.setClassLoader(pluginManager.uberClassLoader);
ClassLoader classLoaderToAssign;
if (MissingClassTelemetry.enabled() && !(pluginManager.uberClassLoader instanceof CatcherClassLoader)) {
classLoaderToAssign = new CatcherClassLoader(pluginManager.uberClassLoader);
} else {
classLoaderToAssign = pluginManager.uberClassLoader;
}
webApp.setClassLoader(classLoaderToAssign);
webApp.setJsonInErrorMessageSanitizer(RedactSecretJsonInErrorMessageSanitizer.INSTANCE);
TypedFilter typedFilter = new TypedFilter();
......@@ -928,7 +948,10 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
webApp.setDispatchValidator(new StaplerDispatchValidator());
webApp.setFilteredDispatchTriggerListener(actionListener);
adjuncts = new AdjunctManager(servletContext, pluginManager.uberClassLoader,"adjuncts/"+SESSION_HASH, TimeUnit.DAYS.toMillis(365));
//Telemetry: add interceptor classloader
//These lines allows the catcher to be present on Thread.currentThread().getContextClassLoader() in every plugin which
//allow us to detect failures in every plugin loading classes at this way.
adjuncts = new AdjunctManager(servletContext, classLoaderToAssign, "adjuncts/" + SESSION_HASH, TimeUnit.DAYS.toMillis(365));
ClassFilterImpl.register();
......
......@@ -9,18 +9,17 @@ import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;
import hudson.util.AtomicFileWriter;
import hudson.util.StreamTaskListener;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.io.FileUtils;
/**
* Convenient base implementation for {@link Permalink}s that satisfy
......@@ -46,8 +45,7 @@ import org.apache.commons.io.FileUtils;
*
* <p>
* This base class provides a file-based caching mechanism that avoids
* walking the long build history. The cache is a symlink to the build directory
* where symlinks are supported, and text file that contains the build number otherwise.
* walking the long build history.
*
* <p>
* The implementation transparently tolerates G(B) that goes from true to false over time
......@@ -56,13 +54,17 @@ import org.apache.commons.io.FileUtils;
* from false to true, then call {@link #resolve(Job)} to check the current permalink target
* is up to date, then call {@link #updateCache(Job, Run)} if it needs updating.
*
* @author Kohsuke Kawaguchi
* @since 1.507
*/
public abstract class PeepholePermalink extends Permalink implements Predicate<Run<?,?>> {
/** JENKINS-22822: avoids rereading symlinks */
static final Map<File,String> symlinks = new HashMap<>();
/**
* JENKINS-22822: avoids rereading caches.
* Top map keys are {@link builds} directories.
* Inner maps are from permalink name to build number.
* Synchronization is first on the outer map, then on the inner.
*/
private static final Map<File, Map<String, Integer>> caches = new HashMap<>();
/**
* Checks if the given build satisfies the peep-hole criteria.
......@@ -71,9 +73,8 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
*/
public abstract boolean apply(Run<?,?> run);
/**
* The file in which the permalink target gets recorded.
*/
/** @deprecated No longer used. */
@Deprecated
protected File getPermalinkFile(Job<?,?> job) {
return new File(job.getBuildDir(),getId());
}
......@@ -83,32 +84,27 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
*/
@Override
public Run<?, ?> resolve(Job<?, ?> job) {
File f = getPermalinkFile(job);
Run<?,?> b=null;
try {
String target = readSymlink(f);
if (target!=null) {
int n = Integer.parseInt(Util.getFileName(target));
if (n==RESOLVES_TO_NONE) return null;
b = job.getBuildByNumber(n);
if (b!=null && apply(b))
return b; // found it (in the most efficient way possible)
// the cache is stale. start the search
if (b==null)
b=job.getNearestOldBuild(n);
Map<String, Integer> cache = cacheFor(job.getBuildDir());
int n;
synchronized (cache) {
n = cache.getOrDefault(getId(), 0);
}
if (n == RESOLVES_TO_NONE) {
return null;
}
Run<?, ?> b;
if (n > 0) {
b = job.getBuildByNumber(n);
if (b != null && apply(b)) {
return b; // found it (in the most efficient way possible)
}
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "Failed to read permalink cache:" + f, e);
// if we fail to read the cache, fall back to the re-computation
} catch (NumberFormatException e) {
LOGGER.log(Level.WARNING, "Failed to parse the build number in the permalink cache:" + f, e);
// if we fail to read the cache, fall back to the re-computation
} catch (IOException e) {
// this happens when the symlink doesn't exist
// (and it cannot be distinguished from the case when the actual I/O error happened
} else {
b = null;
}
// the cache is stale. start the search
if (b == null) {
b = job.getNearestOldBuild(n);
}
if (b==null) {
......@@ -133,72 +129,71 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
return b;
}
/**
* Remembers the value 'n' in the cache for future {@link #resolve(Job)}.
*/
protected void updateCache(@Nonnull Job<?,?> job, @Nullable Run<?,?> b) {
final int n = b==null ? RESOLVES_TO_NONE : b.getNumber();
File cache = getPermalinkFile(job);
cache.getParentFile().mkdirs();
try {
String target = String.valueOf(n);
if (b != null && !new File(job.getBuildDir(), target).exists()) {
// (re)create the build Number->Id symlink
Util.createSymlink(job.getBuildDir(),b.getId(),target,TaskListener.NULL);
private static @Nonnull Map<String, Integer> cacheFor(@Nonnull File buildDir) {
synchronized (caches) {
Map<String, Integer> cache = caches.get(buildDir);
if (cache == null) {
cache = load(buildDir);
caches.put(buildDir, cache);
}
writeSymlink(cache, target);
} catch (IOException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Failed to update "+job+" "+getId()+" permalink for " + b, e);
cache.delete();
return cache;
}
}
// File.exists returns false for a link with a missing target, so for Java 6 compatibility we have to use this circuitous method to see if it was created.
private static boolean exists(File link) {
File[] kids = link.getParentFile().listFiles();
return kids != null && Arrays.asList(kids).contains(link);
}
static String readSymlink(File cache) throws IOException, InterruptedException {
synchronized (symlinks) {
String target = symlinks.get(cache);
if (target != null) {
LOGGER.log(Level.FINE, "readSymlink cached {0} → {1}", new Object[] {cache, target});
return target;
private static @Nonnull Map<String, Integer> load(@Nonnull File buildDir) {
Map<String, Integer> cache = new TreeMap<>();
File storage = storageFor(buildDir);
if (storage.isFile()) {
try {
Files.lines(storage.toPath(), StandardCharsets.UTF_8).forEach(line -> {
int idx = line.indexOf(' ');
if (idx == -1) {
return;
}
try {
cache.put(line.substring(0, idx), Integer.parseInt(line.substring(idx + 1)));
} catch (NumberFormatException x) {
LOGGER.log(Level.WARNING, "failed to read " + storage, x);
}
});
} catch (IOException x) {
LOGGER.log(Level.WARNING, "failed to read " + storage, x);
}
LOGGER.fine(() -> "loading from " + storage + ": " + cache);
}
String target = Util.resolveSymlink(cache);
if (target==null && cache.exists()) {
// if this file isn't a symlink, it must be a regular file
target = FileUtils.readFileToString(cache,"UTF-8").trim();
}
LOGGER.log(Level.FINE, "readSymlink {0} → {1}", new Object[] {cache, target});
synchronized (symlinks) {
symlinks.put(cache, target);
}
return target;
return cache;
}
static void writeSymlink(File cache, String target) throws IOException, InterruptedException {
LOGGER.log(Level.FINE, "writeSymlink {0} → {1}", new Object[] {cache, target});
synchronized (symlinks) {
symlinks.put(cache, target);
}
StringWriter w = new StringWriter();
StreamTaskListener listener = new StreamTaskListener(w);
Util.createSymlink(cache.getParentFile(),target,cache.getName(),listener);
// Avoid calling resolveSymlink on a nonexistent file as it will probably throw an IOException:
if (!exists(cache) || Util.resolveSymlink(cache)==null) {
// symlink not supported. use a regular file
AtomicFileWriter cw = new AtomicFileWriter(cache);
try {
cw.write(target);
cw.commit();
} finally {
cw.abort();
}
static @Nonnull File storageFor(@Nonnull File buildDir) {
return new File(buildDir, "permalinks");
}
/**
* Remembers the value 'n' in the cache for future {@link #resolve(Job)}.
*/
protected void updateCache(@Nonnull Job<?,?> job, @CheckForNull Run<?,?> b) {
File buildDir = job.getBuildDir();
Map<String, Integer> cache = cacheFor(buildDir);
synchronized (cache) {
cache.put(getId(), b == null ? RESOLVES_TO_NONE : b.getNumber());
File storage = storageFor(buildDir);
LOGGER.fine(() -> "saving to " + storage + ": " + cache);
try {
AtomicFileWriter cw = new AtomicFileWriter(storage);
try {
for (Map.Entry<String, Integer> entry : cache.entrySet()) {
cw.write(entry.getKey());
cw.write(' ');
cw.write(Integer.toString(entry.getValue()));
cw.write('\n');
}
cw.commit();
} finally {
cw.abort();
}
} catch (IOException x) {
LOGGER.log(Level.WARNING, "failed to update " + storage, x);
}
}
}
......@@ -213,8 +208,7 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
for (PeepholePermalink pp : Util.filter(j.getPermalinks(), PeepholePermalink.class)) {
if (pp.resolve(j)==run) {
Run<?,?> r = pp.find(run.getPreviousBuild());
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Updating "+pp.getPermalinkFile(j).getName()+" permalink from deleted "+run.getNumber()+" to "+(r == null ? -1 : r.getNumber()));
LOGGER.fine(() -> "Updating " + pp.getId() + " permalink from deleted " + run + " to " + (r == null ? -1 : r.getNumber()));
pp.updateCache(j,r);
}
}
......@@ -230,8 +224,7 @@ public abstract class PeepholePermalink extends Permalink implements Predicate<R
if (pp.apply(run)) {
Run<?, ?> cur = pp.resolve(j);
if (cur==null || cur.getNumber()<run.getNumber()) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Updating "+pp.getPermalinkFile(j).getName()+" permalink to completed "+run.getNumber());
LOGGER.fine(() -> "Updating " + pp.getId() + " permalink to completed " + run);
pp.updateCache(j,run);
}
}
......
/*
* The MIT License
*
* Copyright 2013 Jesse Glick.
* Copyright (c) 2019 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -22,25 +22,30 @@
* THE SOFTWARE.
*/
package jenkins.model;
package jenkins.telemetry.impl.java11;
import java.io.File;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.Issue;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
public class PeepholePermalinkTest {
@Restricted(NoExternalUse.class)
public class CatcherClassLoader extends ClassLoader {
@Rule public TemporaryFolder tmp = new TemporaryFolder();
@Issue("JENKINS-17681")
@Test public void symlinks() throws Exception {
File link = new File(tmp.getRoot(), "link");
PeepholePermalink.writeSymlink(link, "stuff");
PeepholePermalink.symlinks.clear(); // so we actually test the filesystem
assertEquals("stuff", PeepholePermalink.readSymlink(link));
public CatcherClassLoader(ClassLoader parent) {
super(parent);
}
/**
* Usually, the {@link ClassLoader} calls its parent and finally this method. So if we are here, it's the last
* element of the chain. It doesn't happen in {@link jenkins.util.AntClassLoader} so it has an special management
* on {@link hudson.ClassicPluginStrategy}
*
*
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
ClassNotFoundException e = new ClassNotFoundException(name);
MissingClassTelemetry.reportException(name, e);
throw e;
}
}
/*
* The MIT License
*
* Copyright (c) 2019 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.telemetry.impl.java11;
import javax.annotation.Nonnull;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
/**
* Store an event regarding missing classes. We can already catch ClassNotFoundException and NoClassDefFoundError
*/
class MissingClassEvent {
private String time;
private long occurrences;
private String stackTrace;
private String className;
String getStackTrace() {
return stackTrace;
}
void setStackTrace(String stackTrace) {
this.stackTrace = stackTrace;
}
String getClassName() {
return className;
}
void setClassName(String className) {
this.className = className;
}
MissingClassEvent(@Nonnull String name, @Nonnull Throwable t) {
this.className = name;
StringWriter stackTrace = new StringWriter();
t.printStackTrace(new PrintWriter(stackTrace));
this.stackTrace = stackTrace.toString();
this.time = MissingClassTelemetry.clientDateString();
this.occurrences = 1;
}
String getTime() {
return time;
}
long getOccurrences() {
return occurrences;
}
void setOccurrences(long occurrences) {
this.occurrences = occurrences;
}
void setTime(String time) {
this.time = time;
}
@Override
public String toString() {
return "MissingClassEvent{" +
"time='" + time + '\'' +
", occurrences=" + occurrences +
", stackTrace='" + stackTrace + '\'' +
", className='" + className + '\'' +
'}';
}
}
/*
* The MIT License
*
* Copyright (c) 2019 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.telemetry.impl.java11;
import com.google.common.annotations.VisibleForTesting;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class MissingClassEvents {
/**
* Only 100 exceptions a day (period of telemetry)
*/
@VisibleForTesting
/* package */ static /* final */ int MAX_EVENTS_PER_SEND = 100;
/**
* List of events, one per stack trace.
*/
private ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> events = new ConcurrentHashMap<>(MAX_EVENTS_PER_SEND);
/**
* Add a new exception to the store. If the same exception already exists, it increases the occurrences. If we
* already get the maximum number of exceptions, it doesn't add any value.
* @param name name of the class not found
* @param t the exception to store
* @return the occurrences stored for this throwable. 1 the fist time it's stored. &gt; 1 for successive stores of the
* same <strong>stack trace</strong>. 0 if we already stored MAX_EVENTS_PER_SEND (100) events for a single send.
*/
public long put(String name, @Nonnull Throwable t) {
// A final object to pass it to the function
final AtomicLong occurrences = new AtomicLong();
// We need the key (the stack trace) to be a list and unmodifiable
List<StackTraceElement> key = Collections.unmodifiableList(Arrays.asList(t.getStackTrace()));
events.compute(key, (stackTraceElements, missingClassEvent) -> {
if (missingClassEvent == null) {
// It's a new element, the size will increase
if (events.size() < MAX_EVENTS_PER_SEND) {
// Create the new value
MissingClassEvent newEvent = new MissingClassEvent(name, t);
occurrences.set(1);
return newEvent;
} else {
return null;
}
} else {
// We update the occurrences and the last time it happened
occurrences.set(missingClassEvent.getOccurrences());
missingClassEvent.setOccurrences(occurrences.incrementAndGet());
missingClassEvent.setTime(MissingClassTelemetry.clientDateString());
return missingClassEvent;
}
});
return occurrences.get();
}
/**
* Reinitialize the events happened and return the number of events stored since last execution of this method.
* Used to send via telemetry the events and restart the events store.
* @return the number of events stored since previous call to this method.
*/
@VisibleForTesting
/* package */ synchronized @Nonnull ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> getEventsAndClean() {
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> currentEvents = events;
events = new ConcurrentHashMap<>(MAX_EVENTS_PER_SEND);
return currentEvents;
}
@Override
public String toString() {
return "MissingClassEvents{" +
"events=" + events +
'}';
}
}
/*
* The MIT License
*
* Copyright (c) 2019 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.telemetry.impl.java11;
import com.google.common.annotations.VisibleForTesting;
import hudson.Extension;
import io.jenkins.lib.versionnumber.JavaSpecificationVersion;
import jenkins.model.Jenkins;
import jenkins.telemetry.Telemetry;
import jenkins.util.java.JavaUtils;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Telemetry class to gather information about missing classes when running on java 11. This class sends classes not
* found and in packages related with Java changes from Java 8 to Java 11. See {@link #MOVED_PACKAGES}.
**/
@Extension
@Restricted(NoExternalUse.class)
public class MissingClassTelemetry extends Telemetry {
private static final Logger LOGGER = Logger.getLogger(MissingClassTelemetry.class.getName());
// Store 100 events today
private static MissingClassEvents events = new MissingClassEvents();
// When we begin to gather these data
private final static LocalDate START = LocalDate.of(2019, 4, 1);
// Gather for 2 years (who knows how long people will need to migrate to Java 11)
private final static LocalDate END = START.plusMonths(24);
// The types of exceptions which can be reported
private static final Set reportableExceptions =
new HashSet<Class>(Arrays.asList(ClassNotFoundException.class, NoClassDefFoundError.class));
@VisibleForTesting
/* package */ static final String CIRCULAR_REFERENCE = "Circular reference found on the exception we are analysing to report via telemetry";
/**
* Packages removed from java8 up to java11
* https://blog.codefx.org/java/java-11-migration-guide/
*/
private final static String[] MOVED_PACKAGES = new String[] {"javax.activation", "javax.annotation", "javax.jws",
"javax.rmi", "javax.transaction", "javax.xml.bind", "javax.xml.soap", "javax.xml.ws", "org.omg",
"javax.activity", "com.sun", "sun"};
/**
* Places where a ClassNotFoundException is going to be thrown but it's ignored later in the code, so we
* don't have to send this exception, even though it might be related with java classes of moved packages
*/
private static String[][] IGNORED_PLACES = {
{"hudson.util.XStream2$AssociatedConverterImpl", "findConverter"},
{"org.jenkinsci.plugins.workflow.cps.global.GrapeHack", "hack"},
{"org.codehaus.groovy.runtime.callsite.CallSiteArray", "createCallStaticSite"},
{"groovy.lang.MetaClassImpl", "addProperties"},
// We set the reportException call directly in this method when it's appropriated
{"hudson.PluginManager.UberClassLoader", "findClass"},
{"hudson.ExtensionFinder$GuiceFinder$FaultTolerantScope$1", "get"},
{"hudson.ExtensionFinder$GuiceFinder$SezpozModule", "resolve"},
{"java.beans.Introspector", "findCustomizerClass"},
{"com.sun.beans.finder.InstanceFinder", "instantiate"},
{"com.sun.beans.finder.ClassFinder", "findClass"},
{"java.util.ResourceBundle$Control", "newBundle"},
//hundreds when a job is created
{"org.codehaus.groovy.control.ClassNodeResolver", "tryAsLoaderClassOrScript"},
{"org.kohsuke.stapler.RequestImpl$TypePair", "convertJSON"}
};
@Nonnull
@Override
public String getDisplayName() {
return "Missing classes related with Java updates";
}
@Nonnull
@Override
public LocalDate getStart() {
return START;
}
@Nonnull
@Override
public LocalDate getEnd() {
return END;
}
/**
* To allow asserting this info in tests.
* @return the events gathered.
*/
@VisibleForTesting
/* package */ static MissingClassEvents getEvents() {
return events;
}
/**
* This telemetry is only enabled when running on Java versions newer than Java 8.
* @return true if running on a newer Java version than Java 8
*/
public static boolean enabled() {
return JavaUtils.getCurrentJavaRuntimeVersionNumber().isNewerThan(JavaSpecificationVersion.JAVA_8);
}
@CheckForNull
@Override
public JSONObject createContent() {
// If we are on the time window of this telemetry (checked by the Telemetry class) but we are not running on
// Java > 1.8 (checked here), we don't send anything
if (!enabled()) {
return null;
}
// To avoid sending empty events
JSONArray events = formatEventsAndInitialize();
if (events.size() == 0) {
return null;
}
JSONObject info = new JSONObject();
info.put("core", Jenkins.getVersion() != null ? Jenkins.getVersion().toString() : "UNKNOWN");
info.put("clientDate", clientDateString());
info.put("classMissingEvents", events);
return JSONObject.fromObject(info);
}
/**
* Returns the events gathered as a Map ready to use in a Json object to send via telemetry and clean the map to
* gather another window of events.
* @return the map of missed classes events gathered along this window of telemetry
*/
@Nonnull
private JSONArray formatEventsAndInitialize() {
// Save the current events and clean for next (not this one) telemetry send
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> toReport = MissingClassTelemetry.events.getEventsAndClean();
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Cleaned events for missing classes");
}
return formatEvents(toReport);
}
/**
* Format the events gathered in a map used to create the json object to send via telemetry. The events are named by
* the class not found. But a class could be repeated if it was thrown from several places. The interesting
* pieces of information we want to gather are the places where the {@link ClassNotFoundException} or the
* {@link NoClassDefFoundError} errors happens, rather than the class itself.
* @param events events collected in this telemetry window.
* @return the events formatted in a map.
*/
@Nonnull
private JSONArray formatEvents(@Nonnull ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> events) {
JSONArray jsonEvents = new JSONArray();
events.forEach((stackTrace, event) -> {
JSONObject eventObject = new JSONObject();
eventObject.put("className", event.getClassName());
eventObject.put("class", event.getClassName());
eventObject.put("time", event.getTime());
eventObject.put("occurrences", Long.toString(event.getOccurrences()));
eventObject.put("stacktrace", event.getStackTrace());
jsonEvents.add(eventObject);
});
return jsonEvents;
}
/**
* The current time in the same way as other telemetry implementations.
* @return the UTC time formatted with the pattern yyyy-MM-dd'T'HH:mm'Z'
*/
@Nonnull
static String clientDateString() {
TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
df.setTimeZone(tz); // strip timezone
return df.format(new Date());
}
/**
* Store the exception if it's from a split package of Java. This method report this exception directly, it doesn't
* look into the causes or suppressed exceptions of the exception specified. This method tends to be used in the
* ClassLoader directly. Outside the class loaders is best to use {@link #reportExceptionInside(Throwable)}
* @param name the name of the class
* @param e the throwable to report if needed
*/
public static void reportException(@Nonnull String name, @Nonnull Throwable e) {
if (enabled()) {
//ClassDefFoundError uses / instead of .
name = name.replace('/', '.').trim();
// We call the methods in this order because if the missing class is not java related, we don't loop over the
// stack trace to look if it's not thrown from an ignored place avoiding an impact on performance.
if (isFromMovedPackage(name) && !calledFromIgnoredPlace(e)) {
events.put(name, e);
if (LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING, "Added a missed class for missing class telemetry. Class: " + name, e);
}
}
}
/**
* Determine if the exception specified was thrown from an ignored place
* @param throwable The exception thrown
* @return true if in the stack trace there is an ignored method / class.
*/
private static boolean calledFromIgnoredPlace(@Nonnull Throwable throwable) {
for(String[] ignoredPlace : IGNORED_PLACES) {
if (calledFrom(throwable, ignoredPlace[0], ignoredPlace[1])) {
return true;
}
}
return false;
}
/**
* Check if the throwable was thrown by the class and the method specified.
* @param throwable stack trace to look at
* @param clazz class to look for in the stack trace
* @param method method where the throwable was thrown in the clazz
* @return true if the method of the clazz has thrown the throwable
*/
private static boolean calledFrom (@Nonnull Throwable throwable, @Nonnull String clazz, @Nonnull String method){
StackTraceElement[] trace = throwable.getStackTrace();
for (StackTraceElement el : trace) {
//If the exception has the class and method searched, it's called from there
if (clazz.equals(el.getClassName()) && el.getMethodName().equals(method)) {
return true;
}
}
return false;
}
/**
* Store the exception extracting the class name from the message of the throwable specified. This method report
* this exception directly, it doesn't look into the causes or suppressed exceptions of the exception specified.
* This method tends to be used in the ClassLoader directly. Outside the class loaders is best to use
* {@link #reportExceptionInside(Throwable)}
* @param e the exception to report if needed
*/
private static void reportException(@Nonnull Throwable e) {
if (enabled()) {
String name = e.getMessage();
if (name == null || name.trim().isEmpty()) {
LOGGER.log(Level.INFO, "No class name could be extracted from the throwable to determine if it's reportable", e);
} else {
reportException(name, e);
}
}
}
private static boolean isFromMovedPackage(@Nonnull String clazz) {
for (String movedPackage : MOVED_PACKAGES) {
if (clazz.startsWith(movedPackage)) {
return true;
}
}
return false;
}
/**
* Report the class not found if this exception or any of its causes or suppressed exceptions is related to missed
* classes.
* @param e the exception to look into
*/
public static void reportExceptionInside(@Nonnull Throwable e) {
if (enabled()) {
// Use a Set with equity based on == instead of equal to find cycles
Set<Throwable> exceptionsReviewed = Collections.newSetFromMap(new IdentityHashMap<>());
reportExceptionInside(e, exceptionsReviewed);
}
}
/**
* Find the exception to report among the exception passed and its causes and suppressed exceptions. It does a
* recursion and uses a Set to avoid circular references.
* @param e the exception
* @param exceptionsReviewed the set of already reviewed exceptions
* @return true if a exception was reported
*/
private static boolean reportExceptionInside(@Nonnull Throwable e, @Nonnull Set<Throwable> exceptionsReviewed) {
if (exceptionsReviewed.contains(e)) {
LOGGER.log(Level.WARNING, CIRCULAR_REFERENCE, e);
// Don't go deeper, we already did
return false;
}
// Add this exception to the list of already reviewed exceptions before going deeper in its causes or suppressed
// exceptions
exceptionsReviewed.add(e);
// It this exception is the one searched
if (isMissedClassRelatedException(e)) {
MissingClassTelemetry.reportException(e);
return true;
}
// We search in its cause exception
if (e.getCause() != null) {
if (reportExceptionInside(e.getCause(), exceptionsReviewed)) {
return true;
}
}
// We search in its suppressed exceptions
for (Throwable suppressed: e.getSuppressed()) {
if (suppressed != null) {
if (reportExceptionInside(suppressed, exceptionsReviewed)) {
return true;
}
}
}
// If this exception or its ancestors are not related with missed classes
return false;
}
/**
* Check if the exception specified is related with a missed class, that is, defined in the
* {@link #reportableExceptions} method.
* @param e the exception to look into
* @return true if the class is related with missed classes.
*/
private static boolean isMissedClassRelatedException(Throwable e) {
return reportableExceptions.contains(e.getClass());
}
}
......@@ -17,7 +17,7 @@
*/
package jenkins.util;
import java.nio.file.Files;
import jenkins.telemetry.impl.java11.MissingClassTelemetry;
import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
......@@ -40,6 +40,7 @@ import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
......@@ -1070,27 +1071,36 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener {
if (theClass != null) {
return theClass;
}
if (isParentFirst(classname)) {
try {
theClass = findBaseClass(classname);
log("Class " + classname + " loaded from parent loader " + "(parentFirst)",
Project.MSG_DEBUG);
} catch (ClassNotFoundException cnfe) {
theClass = findClass(classname);
log("Class " + classname + " loaded from ant loader " + "(parentFirst)",
Project.MSG_DEBUG);
}
} else {
try {
theClass = findClass(classname);
log("Class " + classname + " loaded from ant loader", Project.MSG_DEBUG);
} catch (ClassNotFoundException cnfe) {
if (ignoreBase) {
throw cnfe;
//Surround the former logic with a try-catch to report missing class exceptions via Java11 telemetry
try {
if (isParentFirst(classname)) {
try {
theClass = findBaseClass(classname);
log("Class " + classname + " loaded from parent loader " + "(parentFirst)",
Project.MSG_DEBUG);
} catch (ClassNotFoundException cnfe) {
theClass = findClass(classname);
log("Class " + classname + " loaded from ant loader " + "(parentFirst)",
Project.MSG_DEBUG);
}
} else {
try {
theClass = findClass(classname);
log("Class " + classname + " loaded from ant loader", Project.MSG_DEBUG);
} catch (ClassNotFoundException cnfe) {
if (ignoreBase) {
throw cnfe;
}
theClass = findBaseClass(classname);
log("Class " + classname + " loaded from parent loader", Project.MSG_DEBUG);
}
theClass = findBaseClass(classname);
log("Class " + classname + " loaded from parent loader", Project.MSG_DEBUG);
}
} catch (ClassNotFoundException cnfe) {
//To catch CNFE thrown from this.getClass().getClassLoader().loadClass(classToLoad); from a plugin step or
//a plugin page
MissingClassTelemetry.reportException(classname, cnfe);
throw cnfe;
}
if (resolve) {
resolveClass(theClass);
......
......@@ -290,6 +290,11 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) {
return true;
}
}
if (pluginTR.hasClassName('detached')) {
infoContainer.update('<div class="title">' + i18n('detached-disable') + '</div><div class="subtitle">' + i18n('detached-possible-dependents') + '</div>');
return true;
}
return false;
}
......@@ -318,6 +323,11 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) {
return true;
}
if (pluginTR.hasClassName('detached')) {
infoContainer.update('<div class="title">' + i18n('detached-uninstall') + '</div><div class="subtitle">' + i18n('detached-possible-dependents') + '</div>');
return true;
}
return false;
}
......
......@@ -50,6 +50,9 @@ THE SOFTWARE.
data-disabled-dependencies="${%It has one or more disabled dependencies}"
data-enabled-dependents="${%It has one or more enabled dependents}"
data-installed-dependents="${%It has one or more installed dependents}"
data-detached-disable="${%detached-disable}"
data-detached-uninstall="${%detached-uninstall}"
data-detached-possible-dependents="${%detached-possible-dependents}"
/>
<table id="plugins" class="pane bigtable sortable stripped-odd">
<j:choose>
......@@ -67,7 +70,7 @@ THE SOFTWARE.
<th width="1">${%Uninstall}</th>
</tr>
<j:forEach var="p" items="${app.pluginManager.plugins}">
<tr class="plugin ${p.hasMandatoryDependents()?'has-dependents':''} ${(p.hasMandatoryDependents() &amp;&amp; !p.enabled)?'has-dependents-but-disabled':''} ${p.isDeleted()?'deleted':''}" data-plugin-id="${p.shortName}" data-plugin-name="${p.displayName}">
<tr class="plugin ${p.hasMandatoryDependents()?'has-dependents':''} ${(p.hasMandatoryDependents() and !p.enabled)?'has-dependents-but-disabled':''} ${p.isDeleted()?'deleted':''} ${p.detached and p.enabled ? 'detached' : ''}" data-plugin-id="${p.shortName}" data-plugin-name="${p.displayName}">
<j:set var="state" value="${p.enabled?'true':null}"/>
<td class="center pane enable" data="${state}">
<input type="checkbox" checked="${state}" onclick="flip(event)"
......
......@@ -21,3 +21,6 @@
# THE SOFTWARE.
downgradeTo=Downgrade to {0}
requires.restart=This Jenkins instance requires a restart. Changing the state of plugins at this time is strongly discouraged. Restart Jenkins before proceeding.
detached-disable=This plugin may not be safe to disable
detached-uninstall=This plugin may not be safe to uninstall
detached-possible-dependents=Its functionality was at one point moved out of Jenkins core, and another plugin with a core dependency predating the split may be relying on it implicitly.
......@@ -27,6 +27,6 @@ Enabled=Devrede
Name=\u0130sim
Uninstall=Kald\u0131r
Version=Versiyon
New\ plugins\ will\ take\ effect\ once\ you\ restart\ Jenkins=Yeni\ eklentiler\ Jenkins'\u0131\ bir\ kere\ yeniden\ ba\u015flatt\u0131ktan\ sonra\ devreye\ girecektir
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=De\u011fi\u015fiklikler\ Jenkins'\u0131\ yeniden\ ba\u015flatt\u0131ktan\ sonra\ devreye\ girecektir
New\ plugins\ will\ take\ effect\ once\ you\ restart\ Jenkins=Yeni eklentiler Jenkins'\u0131 bir kere yeniden ba\u015flatt\u0131ktan sonra devreye girecektir
Changes\ will\ take\ effect\ when\ you\ restart\ Jenkins=De\u011fi\u015fiklikler Jenkins'\u0131 yeniden ba\u015flatt\u0131ktan sonra devreye girecektir
downgradeTo={0} s\u00FCr\u00FCm\u00FCne al\u00E7alt
......@@ -20,12 +20,12 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
description.1=\
description.1=\
Dit <tt>JENKINS_HOME</tt> ({0}) er n\u00e6sten fyldt. \
N\u00e5r dette direktorie fylder helt op vil det sprede d\u00f8d og \u00f8del\u00e6ggelse, da Jenkins ikke vil kunne gemme mere data.
solution.2=\
solution.2=\
Flyt <tt>JENKINS_HOME</tt> til et st\u00f8rre diskafsnit.
JENKINS_HOME\ is\ almost\ full=JENKINS_HOME\ er\ n\u00e6sten\ fyldt\ op
JENKINS_HOME\ is\ almost\ full=JENKINS_HOME er n\u00e6sten fyldt op
blurb=<tt>JENKINS_HOME</tt> er n\u00e6sten fyldt op
solution.1=Slet nogle filer p\u00e5 dette diskafsnit for at frig\u00f8re mere plads
description.2=For at undg\u00e5 problemer skal du handle nu.
......@@ -22,9 +22,9 @@
JENKINS_HOME\ is\ almost\ full=o JENKINS_HOME est\u00e1 quase cheio
description.2=Para previnir esse problema, fa\u00e7a alguma coisa agora.
description.1=O seu <tt>JENKINS_HOME</tt> ({0}) est\u00e1 quase cheio. \
description.1=O seu <tt>JENKINS_HOME</tt> ({0}) est\u00e1 quase cheio. \
Quando esse diret\u00f3rio estiver lotado ocorrer\u00e3o alguns estragos, pois o Jenkins n\u00e3o pode gravar mais dado nenhum.
solution.2=Mova o <tt>JENKINS_HOME</tt> para uma parti\u00e7\u00e3o maior. \
solution.2=Mova o <tt>JENKINS_HOME</tt> para uma parti\u00e7\u00e3o maior. \
Veja <a href="https://jenkins.io/redirect/migrate-jenkins-home">a nossa Wiki</a> para aprender como fazer isso.
blurb=<tt>JENKINS_HOME</tt> est\u00e1 quase cheio
solution.1=Limpe alguns arquivos dessa parti\u00e7\u00e3o para liberar mais espa\u00e7o.
......@@ -25,7 +25,7 @@ Discard\ Unreadable\ Data=Descartar dados ileg\u00edveis
Version=Vers\u00e3o
Upgrade=Upgrade
Resave\ data\ files\ with\ structure\ changes\ no\ newer\ than\ Jenkins=Regravar os arquivos de dados com a mudan\u00e7a de estrutura sem ser mais nova que o Jenkins
blurb.2=Algumas vezes ocorre erro ao ler os dados (se o plugin adiciona algum dado que depois \u00e9 \
blurb.2=Algumas vezes ocorre erro ao ler os dados (se o plugin adiciona algum dado que depois \u00e9 \
desativado, se o c\u00f3digo de migra\u00e7\u00e3o n\u00e3o estiver escrito com as mudan\u00e7as de estrutura, ou se o Jenkins sofreu \
downgraded depois de tiver escrito algum dado que \u00e9 ileg\u00edvel por uma vers\u00e3o mais antiga). \
Esses erros s\u00e3o logados,mas o dado ileg\u00edvel ser\u00e1 ignorado, permitindo ao Jenkins iniciar e \
......
......@@ -26,11 +26,11 @@ Build\ Artifacts=Yap\u0131land\u0131rma Hatalar\u0131
Changes\ in\ dependency=Ba\u011f\u0131ml\u0131l\u0131ktaki de\u011fi\u015fiklikler
beingExecuted=Yap\u0131land\u0131rma {0} d\u0131r \u00E7al\u0131\u015Fmaktad\u0131r
detail=detay
Not\ yet\ determined=Hen\u00fcz\ belirlenmedi
Not\ yet\ determined=Hen\u00fcz belirlenmedi
Failed\ to\ determine=Belirleme esnas\u0131nda hata olu\u015ftu
log=log
Upstream\ Builds=Upstream\ Yap\u0131land\u0131rmalar
Downstream\ Builds=Downstream\ Yap\u0131land\u0131rmalar
Upstream\ Builds=Upstream Yap\u0131land\u0131rmalar
Downstream\ Builds=Downstream Yap\u0131land\u0131rmalar
none=hi\u00e7birisi
Permalinks=Permalinks
Build\ number=Yap\u0131land\u0131rma numaras\u0131
......
......@@ -20,5 +20,5 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Previous\ Build=\u00d6nceki\ Yap\u0131land\u0131rma
Next\ Build=Sonraki\ Yap\u0131land\u0131rma
Previous\ Build=\u00d6nceki Yap\u0131land\u0131rma
Next\ Build=Sonraki Yap\u0131land\u0131rma
......@@ -24,7 +24,7 @@ Back\ to\ Project=Projeye geri d\u00F6n
Changes=De\u011Fi\u015Fiklikler
Console\ Output=Konsol \u00C7\u0131kt\u0131s\u0131
View\ Build\ Information=in\u015Fa bilgisi g\u00F6r\u00FCnt\u00FCle
View\ as\ plain\ text=D\u00fcz\ metin\ olarak\ g\u00f6ster
View\ as\ plain\ text=D\u00fcz metin olarak g\u00f6ster
Edit\ Build\ Information=S\u00FCr\u00FCm Bilgisini D\u00FCzenle
Status=Durum
raw=ham
......@@ -20,6 +20,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Are\ you\ sure\ about\ deleting\ the\ job?=Bu\ i\u015fi\ silmek\ istedi\u011finize\ emin\ misiniz?
Are\ you\ sure\ about\ deleting\ the\ job?=Bu i\u015fi silmek istedi\u011finize emin misiniz?
Yes=Evet
blurb={0} ''''{1}'''' Silmek istedi\u011Finizden eminmisiniz ?
......@@ -22,7 +22,7 @@
li3=\u00c7al\u0131\u015fma alan\u0131 dizini ({0}), Jenkins''\u0131n kontrol\u00fc d\u0131\u015f\u0131nda silindi.
text=Jenkins''\u0131n bir \u00e7al\u0131\u015fma alan\u0131 yaratmas\u0131 i\u00e7in bir yap\u0131land\u0131rma \u00e7al\u0131\u015ft\u0131r\u0131n.
Error\:\ no\ workspace=Hata\:\ \u00c7al\u0131\u015fma Alan\u0131 mevcut de\u011fil
A\ project\ won't\ have\ any\ workspace\ until\ at\ least\ one\ build\ is\ performed.=Bir\ proje,\ en\ az\u0131ndan\ bir\ yap\u0131land\u0131rma\ \u00e7al\u0131\u015ft\u0131r\u0131lmadan\ bir\ \u00e7al\u0131\u015fma\ alan\u0131na\ sahip\ olamaz.
There's\ no\ workspace\ for\ this\ project.\ Possible\ reasons\ are\:=Bu\ projenin\ bir\ \u00e7al\u0131\u015fma\ alan\u0131\ yok.\ Muhtemel\ sebepler\:
The\ project\ was\ renamed\ recently\ and\ no\ build\ was\ done\ under\ the\ new\ name.=Projenin\ ismi\ yeni\ de\u011fi\u015ftirildi\ ve\ yeni\ isim\ alt\u0131nda\ herhangi\ bir\ yap\u0131land\u0131rma\ \u00e7al\u0131\u015ft\u0131r\u0131lmad\u0131.
Error\:\ no\ workspace=Hata\: \u00c7al\u0131\u015fma Alan\u0131 mevcut de\u011fil
A\ project\ won't\ have\ any\ workspace\ until\ at\ least\ one\ build\ is\ performed.=Bir proje, en az\u0131ndan bir yap\u0131land\u0131rma \u00e7al\u0131\u015ft\u0131r\u0131lmadan bir \u00e7al\u0131\u015fma alan\u0131na sahip olamaz.
There's\ no\ workspace\ for\ this\ project.\ Possible\ reasons\ are\:=Bu projenin bir \u00e7al\u0131\u015fma alan\u0131 yok. Muhtemel sebepler\:
The\ project\ was\ renamed\ recently\ and\ no\ build\ was\ done\ under\ the\ new\ name.=Projenin ismi yeni de\u011fi\u015ftirildi ve yeni isim alt\u0131nda herhangi bir yap\u0131land\u0131rma \u00e7al\u0131\u015ft\u0131r\u0131lmad\u0131.
......@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Advanced\ Project\ Options=Geli\u015fmi\u015f\ Proje\ Se\u00e7enekleri
Advanced\ Project\ Options=Geli\u015fmi\u015f Proje Se\u00e7enekleri
JDK\ to\ be\ used\ for\ this\ project=Bu proje i\u00e7in kullan\u0131lacak olan JDK
default.value=(Varsay\u0131lan)
Tie\ this\ project\ to\ a\ node=Bu projeyi bir noda ba\u011fla
......
......@@ -20,6 +20,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Workspace=\u00c7al\u0131\u015fma\ Alan\u0131
Workspace=\u00c7al\u0131\u015fma Alan\u0131
Last\ Successful\ Artifacts=Son Ba\u015far\u0131l\u0131 Artefaktlar
Recent\ Changes=Son De\u011fi\u015fiklikler
......@@ -23,4 +23,4 @@
newJob=L\u00FCtfen ba\u015Flamak i\u00E7in <a href="newJob">yeni i\u015Fler olu\u015Fturun</a>
login=Yeni i\u015F olu\u015Fturmak i\u00E7in l\u00FCtfen <a href="{0}/{1}?from={2}">giri\u015F</a> yap\u0131n.
signup=E\u011fer bir hesab\u0131n\u0131z yoksa, hemen <a href="signup">kaydolun</a>.
Welcome\ to\ Jenkins\!=Jenkins'a\ Ho\u015fgeldiniz\!
Welcome\ to\ Jenkins\!=Jenkins'a Ho\u015fgeldiniz\!
......@@ -24,5 +24,5 @@ Back\ to\ List=Listeye D\u00f6n
Build\ History=Yap\u0131land\u0131rma Ge\u00e7mi\u015fi
Configure=Yap\u0131land\u0131r
Load\ Statistics=Y\u00FCklenme istatistikleri
Script\ Console=Script\ Konsolu
Script\ Console=Script Konsolu
Status=Durum
......@@ -20,6 +20,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
all\ files\ in\ zip=Zip\ halinde\ t\u00fcm\ dosyalar
all\ files\ in\ zip=Zip halinde t\u00fcm dosyalar
No\ files\ in\ directory=Dizin i\u00e7erisinde dosya bulunmuyor
view=G\u00F6r\u00FCn\u00FCm
......@@ -20,5 +20,5 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Console\ Output=Konsol\ Ç\u0131kt\u0131s\u0131
Console\ Output=Konsol \u00C7\u0131kt\u0131s\u0131
skipSome={0,number,integer} KB atlan\u0131yor.. <a href="{1}">T\u00FCm Tutanak</a>
......@@ -24,4 +24,4 @@ My\ Views=Benim G\u00F6r\u00FCn\u00FCmlerim
People=\u0130nsanlar
Status=Durum
Builds=Yap\u0131land\u0131rmalar
Configure=Konfig\u00fcrasyonu\ De\u011fi\u015ftir
Configure=Konfig\u00fcrasyonu De\u011fi\u015ftir
......@@ -20,5 +20,5 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Are\ you\ sure\ about\ deleting\ the\ view?=G\u00f6r\u00fcnt\u00fcy\u00fc\ silmek\ istedi\u011finize\ emin\ misiniz?
Are\ you\ sure\ about\ deleting\ the\ view?=G\u00f6r\u00fcnt\u00fcy\u00fc silmek istedi\u011finize emin misiniz?
Yes=Evet
......@@ -45,7 +45,9 @@ THE SOFTWARE.
<!-- for screen resolution detection -->
<l:yui module="cookie" />
<script>
YAHOO.util.Cookie.set("screenResolution", screen.width+"x"+screen.height);
YAHOO.util.Cookie.set("screenResolution", screen.width+"x"+screen.height, {
path: "/"
});
</script>
</l:header>
</l:layout>
......
......@@ -20,4 +20,4 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Dashboard=Kontrol\ Merkezi
Dashboard=Kontrol Merkezi
......@@ -20,8 +20,8 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#JobName=\u0130\u015f\ ad\u0131
#CopyExisting=Varolan\ bir\ i\u015fi\ kopyala
#JobName=\u0130\u015f ad\u0131
#CopyExisting=Varolan bir i\u015fi kopyala
#Copy\ from=Kopyalanacak i\u015f
CopyExisting=Varolandan kopyala {0}
JobName=isim
......@@ -22,7 +22,7 @@
Dismiss=\
\u041e\u0442\u043a\u0430\u0437\u0432\u0430\u043d\u0435
# Jenkins took some agents offline because <a href="{0}/computer/">their key health metrics</a> went below a threshold. \
# Jenkins took some agents offline because <a href="{0}/computer/">their key health metrics</a> went below a threshold. \
# If you don\u2019t want Jenkins to do this, \
# <a href="{0}/computer/configure">change the setting</a>.
blurb=\
......
......@@ -20,4 +20,4 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
No\ changes.=Herhangi\ bir\ de\u011fi\u015fiklik\ yok.
No\ changes.=Herhangi bir de\u011fi\u015fiklik yok.
......@@ -21,4 +21,4 @@
# THE SOFTWARE.
No\ builds.=Herhangi bir yap\u0131land\u0131rm\a yok.
No\ changes\ in\ any\ of\ the\ builds.=Yap\u0131land\u0131rmalarda\ hi\u00e7birinde\ de\u011fi\u015fiklik\ yok.
No\ changes\ in\ any\ of\ the\ builds.=Yap\u0131land\u0131rmalarda hi\u00e7birinde de\u011fi\u015fiklik yok.
......@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Allows an agent to be launched using <a href="https://en.wikipedia.org/wiki/Java_Web_Start" target="_blank">Java Web Start</a>.<br>\
# Allows an agent to be launched using <a href="https://en.wikipedia.org/wiki/Java_Web_Start" target="_blank">Java Web Start</a>.<br> \
# In this case, a JNLP file must be opened on the agent machine, which will \
# establish a TCP connection to the Jenkins master.<br>\
# This means that the agent need not be reachable from the master; the agent \
......
......@@ -20,10 +20,10 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Recorded\ Fingerprints=Kay\u0131tl\u0131\ parmakizleri
Recorded\ Fingerprints=Kay\u0131tl\u0131 parmakizleri
File=Dosya
Original\ owner=Orijinal\ sahibi
Original\ owner=Orijinal sahibi
Age=Ya\u015f
more\ details=daha\ fazla\ detay
more\ details=daha fazla detay
outside\ Jenkins=Jenkins'\u0131n d\u0131\u015f\u0131nda
this\ build=bu yap\u0131land\u0131rma
......@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Jenkins passes <a href="{0}/env-vars.html" target="_new">various environment \
# Jenkins passes <a href="{0}/env-vars.html" target="_new">various environment \
# variables</a> to Maven, which you can access from Maven as "$&#123;env.VARIABLENAME}".
para2=\
Jenkins \u043f\u0440\u0435\u0434\u0430\u0432\u0430 <a href="{0}/env-vars.html" target="_new">\u043d\u044f\u043a\u043e\u0438 \u043f\u0440\u043e\u043c\u0435\u043d\u043b\u0438\u0432\u0438 \u043d\u0430\
......
......@@ -22,7 +22,7 @@
# There are more SCM polling activities scheduled than handled, so \
# the threads are not keeping up with the demands. \
# <a href='descriptor/hudson.triggers.SCMTrigger/'>Check if your polling is \
# <a href='descriptor/hudson.triggers.SCMTrigger/'>Check if your polling is \
# hanging, and/or increase the number of threads if necessary</a>.
blurb=\
\u0417\u0430\u044f\u0432\u043a\u0438\u0442\u0435 \u0437\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u0442\u0430 \u0437\u0430 \u043a\u043e\u043d\u0442\u0440\u043e\u043b \u043d\u0430 \u0432\u0435\u0440\u0441\u0438\u0438 \u0441\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0432\u0430\u0442 \u043f\u043e-\u0431\u0430\u0432\u043d\u043e\
......
......@@ -22,7 +22,7 @@
# There are more SCM polling activities scheduled than handled, so \
# the threads are not keeping up with the demands. \
# <a href='descriptor/hudson.triggers.SCMTrigger/'>Check if your polling is \
# <a href='descriptor/hudson.triggers.SCMTrigger/'>Check if your polling is \
# hanging, and/or increase the number of threads if necessary</a>.
blurb=Existem mais atividades de verifica\u00E7\u00E3o de SCM agendadas do que gerenciadas, por isso as threads n\u00E3o est\u00E3o acompanhando as demandas. <a href="{0}/descriptor/hudson.triggers.SCMTrigger/">Verifique se as verifica\u00E7\u00F5es est\u00E3o pendentes e aumente o n\u00FAmero de threads se necess\u00E1rio</a>.
......@@ -24,5 +24,5 @@ message=\
Jenkins, ''{0}'' dizinini kullanan birden fazla Jenkins uygulamas\u0131 oldu\u011funu belirledi.\
Bu Jenkins''\u0131n garip davran\u0131\u015flar sergilemesine sebep olabilir, durumu en k\u0131sa zamanda d\u00fczeltin
Error=Hata
This\ Jenkins=Bu\ Jenkins
Other\ Jenkins=Di\u011fer\ Jenkins
This\ Jenkins=Bu Jenkins
Other\ Jenkins=Di\u011fer Jenkins
......@@ -20,5 +20,5 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Please\ wait\ while\ Jenkins\ is\ getting\ ready\ to\ work=Jenkins\ kullan\u0131ma haz\u0131r\ oluncaya\ kadar\ l\u00fctfen\ bekleyiniz
Your\ browser\ will\ reload\ automatically\ when\ Jenkins\ is\ ready.=Jenkins\ haz\u0131r\ oldu\u011funda\ taray\u0131c\u0131n\u0131z\ otomatik\ olarak\ yeniden\ y\u00fcklenecektir.
Please\ wait\ while\ Jenkins\ is\ getting\ ready\ to\ work=Jenkins kullan\u0131ma haz\u0131r oluncaya kadar l\u00fctfen bekleyiniz
Your\ browser\ will\ reload\ automatically\ when\ Jenkins\ is\ ready.=Jenkins haz\u0131r oldu\u011funda taray\u0131c\u0131n\u0131z otomatik olarak yeniden y\u00fcklenecektir.
......@@ -34,7 +34,7 @@ de seguran\u00e7a, poder\u00e1 desativ\u00e1-lo.
Error=Erro
# \
# For how to turn off security manager in your container, refer to \
# <a href="https://jenkins.io/redirect/troubleshooting/disable-security-manager"> \
# <a href="https://jenkins.io/redirect/troubleshooting/disable-security-manager"> \
# Container-specific documentations</a> of Jenkins.
errorMessage.2=Para desativar o gerenciador de seguran\u00e7a, \
Documenta\u00e7\u00e3o especifica sobre containers</a> do Jenkins
......@@ -31,9 +31,9 @@ SystemInfoLink.DisplayName=Sistem Bilgisi
SystemInfoLink.Description=Sorun \u00E7\u00F6zebilmek i\u00E7in \u00E7e\u015Fitli ortam de\u011Fi\u015Fkenlerini g\u00F6sterir.
ConsoleLink.DisplayName=Script Konsolu
ConsoleLink.Description=Y\u00F6netim/sorun \u00E7\u00F6zme/te\u015Fhis i\u00E7in scriptleri \u00E7al\u0131\u015Ft\u0131r\u0131r.
ShutdownLink.DisplayName_cancel=Kapatma i\u015Flemini iptal\ et
ShutdownLink.DisplayName_cancel=Kapatma i\u015Flemini iptal et
ShutdownLink.DisplayName_prepare=Kapatma i\u015Flemine haz\u0131rlan
ShutdownLink.Description=Sistemin g\u00FCvenli bir \u015Fekilde kapat\u0131labilmesi i\u00E7in, yeni yap\u0131land\u0131rma \u00E7al\u0131\u015Ft\u0131r\u0131lmas\u0131n\u0131 durdurur.
ConfigureLink.DisplayName=Sistem Konfig\u00FCrasyonunu\ De\u011Fi\u015Ftir
ConfigureLink.DisplayName=Sistem Konfig\u00FCrasyonunu De\u011Fi\u015Ftir
ConfigureLink.Description=Evrensel ayarlar\u0131 ve path'lari konfig\u00FCre et
PluginsLink.Description=Jenkins fonksiyonalitesini art\u0131rmak i\u00E7in kullan\u0131lan eklentileri ekle, \u00E7\u0131kar, devreye al veya devre d\u0131\u015F\u0131 b\u0131rak
......@@ -20,9 +20,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Check\ File\ Fingerprint=Dosya\ \u00fczerindeki\ parmakizini\ kontrol\ et
File\ to\ check=Kontrol\ edilecek\ dosya
Check=Kontrol\ Et
Check\ File\ Fingerprint=Dosya \u00fczerindeki parmakizini kontrol et
File\ to\ check=Kontrol edilecek dosya
Check=Kontrol Et
description=\
Bir jar dosyan\u0131z var ve hangi versiyon oldu\u011funu bilmiyor musunuz?<br /> \
Parmakizini Jenkins i\u00e7erisindeki veritaban\u0131nda aratarak bulabilirsiniz.
......
......@@ -21,5 +21,5 @@
# THE SOFTWARE.
If\ you\ are\ a\ system\ administrator\ and\ suspect\ this\ to\ be\ a\ configuration\ problem,\ see\ the\ server\ console\ output\ for\ more\ details.=E\u011Fer bir sistem y\u00F6neticisi vard\u0131r ve bu bir yap\u0131land\u0131rma sorunu oldu\u011Fundan \u015F\u00FCpheleniyorsan\u0131z, sunucu konsolu \u00E7\u0131k\u0131\u015F\u0131 Daha fazla bilgi i\u00E7in bkz
Invalid\ login\ information.\ Please\ try\ again.=Ge\u00e7ersiz\ giri\u015f\ bilgisi.\ L\u00fctfen\ tekrar\ deneyiniz.
Try\ again=Tekrar\ deneyiniz
Invalid\ login\ information.\ Please\ try\ again.=Ge\u00e7ersiz giri\u015f bilgisi. L\u00fctfen tekrar deneyiniz.
Try\ again=Tekrar deneyiniz
......@@ -23,5 +23,5 @@
signUp=E\u011fer \u00fcye de\u011filseniz <a href="signup">bir kullan\u0131c\u0131 yarat\u0131n</a>.
User=Kullan\u0131c\u0131
Password=\u015eifre
Remember\ me\ on\ this\ computer=Beni\ bu\ bilgisayarda\ hat\u0131rla
Remember\ me\ on\ this\ computer=Beni bu bilgisayarda hat\u0131rla
login=giri\u015f
......@@ -19,7 +19,7 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Twitter\:\ @jenkinsci=Twitter\:\ @jenkinsci
Twitter\:\ @jenkinsci=Twitter\: @jenkinsci
Mailing\ Lists=Lista mailingowa
Oops!=Oops!
Bug\ tracker=Zg\u0142aszanie b\u0142\u0119d\u00F3w
......
......@@ -25,7 +25,7 @@ body=\
E\u011fer birbirine ba\u011fl\u0131 projeleriniz varsa, Jenkins <a href="https://jenkins.io/redirect/fingerprint">parmakizi deste\u011fi</a>\
ile olu\u015fturulan kay\u0131tlar\u0131 kullanarak hangi upstream projenin hangi downstream proje taraf\u0131ndan\
kullan\u0131ld\u0131\u011f\u0131n\u0131 takip edebilir.
For\ this\ feature\ to\ work,\ the\ following\ conditions\ need\ to\ be\ met\:=Bu\ \u00f6zelli\u011fin\ \u00e7al\u0131\u015fabilmesi\ i\u00e7in\ devam\u0131ndaki\ \u015fartlar\u0131n\ sa\u011flanmas\u0131\ gerekir:
The\ upstream\ project\ records\ the\ fingerprints\ of\ its\ build\ artifacts.=Upstream\ proje\, kendi yap\u0131land\u0131rma artefaktlar\u0131n\u0131n\ parmakizlerini\ kaydeder.
The\ downstream\ project\ records\ the\ fingerprints\ of\ the\ upstream\ jar\ files\ it\ uses.=Downstream\ proje,\ upstream\ projeden\ kulland\u0131\u011f\u0131\ jar\'lar\u0131n\ parmakizini\ kaydeder.
This\ allows\ Jenkins\ to\ correlate\ two\ projects.=Bu,\ Jenkins\'a\ iki\ projeyi\ kar\u015f\u0131l\u0131kl\u0131\ ili\u015fkilendirme\ yetene\u011fini\ verir.
For\ this\ feature\ to\ work,\ the\ following\ conditions\ need\ to\ be\ met\:=Bu \u00f6zelli\u011fin \u00e7al\u0131\u015fabilmesi i\u00e7in devam\u0131ndaki \u015fartlar\u0131n sa\u011flanmas\u0131 gerekir:
The\ upstream\ project\ records\ the\ fingerprints\ of\ its\ build\ artifacts.=Upstream proje\, kendi yap\u0131land\u0131rma artefaktlar\u0131n\u0131n parmakizlerini kaydeder.
The\ downstream\ project\ records\ the\ fingerprints\ of\ the\ upstream\ jar\ files\ it\ uses.=Downstream proje, upstream projeden kulland\u0131\u011f\u0131 jar\'lar\u0131n parmakizini kaydeder.
This\ allows\ Jenkins\ to\ correlate\ two\ projects.=Bu, Jenkins\'a iki projeyi kar\u015f\u0131l\u0131kl\u0131 ili\u015fkilendirme yetene\u011fini verir.
......@@ -21,6 +21,6 @@
# THE SOFTWARE.
Project\ Relationship=Projelerin \u0130li\u015fkisi
upstream\ project=upstream\ proje
downstream\ project=downstream\ proje
upstream\ project=upstream proje
downstream\ project=downstream proje
Compare=Kar\u015f\u0131la\u015ft\u0131r
......@@ -20,5 +20,5 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
System\ Properties=Sistem\ \u00d6zellikleri
Environment\ Variables=Ortam\ De\u011fi\u015fkenleri
System\ Properties=Sistem \u00d6zellikleri
Environment\ Variables=Ortam De\u011fi\u015fkenleri
......@@ -20,5 +20,5 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
System\ Admin\ e-mail\ address=Sistem\ Admin\ e-posta\ Adresi
Jenkins\ URL=Jenkins\ URL'i
System\ Admin\ e-mail\ address=Sistem Admin e-posta Adresi
Jenkins\ URL=Jenkins URL'i
......@@ -32,7 +32,7 @@ Hudson.NodeBeingRemoved=Node bliver fjernet
Hudson.UnsafeChar=''{0}'' er et usikkert tegn
Hudson.ViewAlreadyExists=En visning ved navn "{0}" findes allerede
Hudson.ViewName=Alle
Hudson.NotUsesUTF8ToDecodeURL=\
Hudson.NotUsesUTF8ToDecodeURL=\
Din container bruger ikke UTF-8 til at afkode URLer. Hvis du bruger ikke-ASCII tegn i jobnavne etc, \
vil dette skabe problemer.
Hudson.ReadPermission.Description=\
......
......@@ -28,3 +28,6 @@ jdk-tool 2.112 1.0
# JENKINS-55681
jaxb 2.163 2.3.0 11
#JENKINS-43610 Split Trilead out from Core
trilead-api 2.184 1.0.4
......@@ -35,7 +35,7 @@ THE SOFTWARE.
<st:attribute name="title" use="required" />
</st:documentation>
<!--
Without @checked, optionalBlock will try to coarse an object to a boolean, which fails,
Without @checked, optionalBlock will try to coerce an object to a boolean, which fails,
so override @checked manually.
-->
<f:optionalBlock field="${field}" title="${title}" checked="${instance[field]!=null}">
......@@ -43,4 +43,4 @@ THE SOFTWARE.
<j:set var="instance" value="${instance[field]}"/>
<st:include from="${descriptor}" page="${descriptor.configPage}" />
</f:optionalBlock>
</j:jelly>
\ No newline at end of file
</j:jelly>
......@@ -27,5 +27,5 @@ offline=\u00e7evrim d\u0131\u015f\u0131
Dead=\u00d6l\u00fc
Idle=Beklemede
Building=Yap\u0131land\u0131r\u0131yor
terminate\ this\ build=Bu yap\u0131land\u0131rmay\u0131\ durdur
terminate\ this\ build=Bu yap\u0131land\u0131rmay\u0131 durdur
suspended=durdurulmu\u015f
......@@ -20,5 +20,5 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Repository\ browser=Repository\ taray\u0131c\u0131s\u0131
Repository\ browser=Repository taray\u0131c\u0131s\u0131
Auto=Otomatik
......@@ -20,4 +20,4 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Source\ Code\ Management=Kaynak\ Kodu\ Y\u00f6netimi
Source\ Code\ Management=Kaynak Kodu Y\u00f6netimi
......@@ -20,5 +20,5 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Upstream\ Projects=Upstream\ Projeler
Downstream\ Projects=Downstream\ Projeler
Upstream\ Projects=Upstream Projeler
Downstream\ Projects=Downstream Projeler
......@@ -22,6 +22,6 @@
Build\ Queue=Yap\u0131land\u0131rma Listesi{0,choice,0#|0< ({0,number})}
No\ builds\ in\ the\ queue.=S\u0131rada bekleyen yap\u0131land\u0131rma yok.
Jenkins\ is\ going\ to\ shut\ down.\ No\ further\ builds\ will\ be\ performed.=Jenkins\ kapat\u0131lacakt\u0131r.\ Bundan\ sonra\ ba\u015fka\ yap\u0131land\u0131rma\ ba\u015flat\u0131lmayacakt\u0131r.
Jenkins\ is\ going\ to\ shut\ down.\ No\ further\ builds\ will\ be\ performed.=Jenkins kapat\u0131lacakt\u0131r. Bundan sonra ba\u015fka yap\u0131land\u0131rma ba\u015flat\u0131lmayacakt\u0131r.
WaitingFor={0} bekleniyor
cancel=iptal
......@@ -33,7 +33,7 @@ description2=\
It\ is\ not\ possible\ to\ run\ scripts\ when\ slave\ is\ offline.=\
\u041a\u043e\u0433\u0430\u0442\u043e \u043f\u043e\u0434\u0447\u0438\u043d\u0435\u043d\u0438\u044f\u0442 \u043a\u043e\u043c\u043f\u044e\u0442\u044a\u0440 \u043d\u0435 \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f, \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0435 \u0438\u0437\u043f\u044a\u043b\u043d\u044f\u0432\u0430\u0442 \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0432\u0435.
# \
# Type in an arbitrary <a href="http://www.groovy-lang.org">Groovy script</a> and \
# Type in an arbitrary <a href="http://www.groovy-lang.org">Groovy script</a> and \
# execute it on the server. Useful for trouble-shooting and diagnostics. \
# Use the \u2018println\u2019 command to see the output (if you use <tt>System.out</tt>, \
# it will go to the server\u2019s stdout, which is harder to see.) Example:
......
......@@ -20,6 +20,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Script\ Console=Script\ Konsolu
Script\ Console=Script Konsolu
Run=\u0130al\u0131\u015ft\u0131r
Result=Sonu\u00e7
......@@ -48,7 +48,7 @@ complete {
rewriteLicense([], license("BSD License","http://jaxen.codehaus.org/license.html"))
}
match("*:dom4j") {
match("org.jenkins-ci.dom4j:dom4j") {
rewriteLicense([],license("BSD License","http://dom4j.sourceforge.net/dom4j-1.6.1/license.html"))
}
......
......@@ -76,7 +76,7 @@ THE SOFTWARE.
</issueManagement>
<properties>
<revision>2.184</revision>
<revision>2.186</revision>
<changelist>-SNAPSHOT</changelist>
<!-- *.html files are in UTF-8, and *.properties are in iso-8859-1, so this configuration is actually incorrect,
......@@ -271,7 +271,7 @@ THE SOFTWARE.
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
<version>1.12</version>
</dependency>
<dependency>
......
......@@ -95,8 +95,8 @@ public class JnlpAccessWithSecuredHudsonTest {
XmlPage jnlp = (XmlPage) wc.goTo("computer/test/slave-agent.jnlp","application/x-java-jnlp-file");
URL baseUrl = jnlp.getUrl();
Document dom = new DOMReader().read(jnlp.getXmlDocument());
for( Element jar : (List<Element>)dom.selectNodes("//jar") ) {
URL url = new URL(baseUrl,jar.attributeValue("href"));
for( Object jar : dom.selectNodes("//jar") ) {
URL url = new URL(baseUrl,((org.dom4j.Element)jar).attributeValue("href"));
System.out.println(url);
// now make sure that these URLs are unprotected
......
......@@ -34,13 +34,11 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.FilePath;
import hudson.Functions;
import hudson.Launcher;
import hudson.Util;
import hudson.maven.MavenModuleSet;
import hudson.scm.NullSCM;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import hudson.security.GlobalMatrixAuthorizationStrategy;
import hudson.tasks.ArtifactArchiver;
import hudson.tasks.BatchFile;
import hudson.tasks.BuildTrigger;
import hudson.tasks.Shell;
......@@ -50,8 +48,6 @@ import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
import hudson.util.OneShotEvent;
import hudson.util.StreamTaskListener;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
......@@ -60,11 +56,9 @@ import java.util.ResourceBundle;
import java.util.Vector;
import java.util.concurrent.Future;
import jenkins.model.Jenkins;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
......@@ -258,65 +252,6 @@ public class AbstractProjectTest {
}
}
@Test
@Issue("JENKINS-1986")
public void buildSymlinks() throws Exception {
Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows());
FreeStyleProject job = j.createFreeStyleProject();
job.getBuildersList().add(new Shell("echo \"Build #$BUILD_NUMBER\"\n"));
FreeStyleBuild build = job.scheduleBuild2(0, new Cause.UserCause()).get();
File lastSuccessful = new File(job.getRootDir(), "lastSuccessful"),
lastStable = new File(job.getRootDir(), "lastStable");
// First build creates links
assertSymlinkForBuild(lastSuccessful, 1);
assertSymlinkForBuild(lastStable, 1);
FreeStyleBuild build2 = job.scheduleBuild2(0, new Cause.UserCause()).get();
// Another build updates links
assertSymlinkForBuild(lastSuccessful, 2);
assertSymlinkForBuild(lastStable, 2);
// Delete latest build should update links
build2.delete();
assertSymlinkForBuild(lastSuccessful, 1);
assertSymlinkForBuild(lastStable, 1);
// Delete all builds should remove links
build.delete();
assertFalse("lastSuccessful link should be removed", lastSuccessful.exists());
assertFalse("lastStable link should be removed", lastStable.exists());
}
private static void assertSymlinkForBuild(File file, int buildNumber)
throws IOException, InterruptedException {
assert file.exists() : "should exist and point to something that exists";
assert Util.isSymlink(file) : "should be symlink";
String s = FileUtils.readFileToString(new File(file, "log"));
assert s.contains("Build #" + buildNumber + "\n") : "link should point to build #$buildNumber, but link was: ${Util.resolveSymlink(file, TaskListener.NULL)}\nand log was:\n$s";
}
@Test
@Issue("JENKINS-2543")
public void symlinkForPostBuildFailure() throws Exception {
Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows());
// Links should be updated after post-build actions when final build result is known
FreeStyleProject job = j.createFreeStyleProject();
job.getBuildersList().add(new Shell("echo \"Build #$BUILD_NUMBER\"\n"));
FreeStyleBuild build = job.scheduleBuild2(0, new Cause.UserCause()).get();
assert Result.SUCCESS == build.getResult();
File lastSuccessful = new File(job.getRootDir(), "lastSuccessful"),
lastStable = new File(job.getRootDir(), "lastStable");
// First build creates links
assertSymlinkForBuild(lastSuccessful, 1);
assertSymlinkForBuild(lastStable, 1);
// Archive artifacts that don't exist to create failure in post-build action
job.getPublishersList().add(new ArtifactArchiver("*.foo", "", false, false));
build = job.scheduleBuild2(0, new Cause.UserCause()).get();
assert Result.FAILURE == build.getResult();
// Links should not be updated since build failed
assertSymlinkForBuild(lastSuccessful, 1);
assertSymlinkForBuild(lastStable, 1);
}
/* TODO too slow, seems capable of causing testWorkspaceLock to time out:
@Test
@Issue("JENKINS-15156")
......
......@@ -23,10 +23,11 @@
*/
package hudson.security;
import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.util.Cookie;
import hudson.security.captcha.CaptchaSupport;
import org.hamcrest.CoreMatchers;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
......@@ -34,9 +35,11 @@ import org.jvnet.hudson.test.JenkinsRule;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Random;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
......@@ -79,4 +82,61 @@ public class SecurityRealmTest {
public void generateImage(String id, OutputStream ios) throws IOException {
}
}
static void addSessionCookie(CookieManager manager, String domain, String path, Date date) {
manager.addCookie(new Cookie(domain, "JSESSIONID."+Integer.toHexString(new Random().nextInt()),
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
path,
date,
false));
}
@Test
public void many_sessions_logout() throws Exception {
final String WILL_NOT_BE_SENT = "/will-not-be-sent";
final String LOCALHOST = "localhost";
final String JSESSIONID = "JSESSIONID";
JenkinsRule.WebClient wc = j.createWebClient();
CookieManager manager = wc.getCookieManager();
manager.setCookiesEnabled(true);
wc.goTo("login");
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, 1);
Date tomorrow = calendar.getTime();
Collections.nCopies(8, 1)
.stream()
.forEach(i -> addSessionCookie(manager, LOCALHOST, "/jenkins", tomorrow));
addSessionCookie(manager, LOCALHOST, WILL_NOT_BE_SENT, tomorrow);
HtmlPage page = wc.goTo("logout");
int unexpectedSessionCookies = 2;
StringBuilder builder = new StringBuilder();
builder.append("Session cookies: ");
for (Cookie cookie : manager.getCookies()) {
if (cookie.getName().startsWith(JSESSIONID)) {
String path = cookie.getPath();
builder.append(cookie.getName());
if (path != null)
builder.append("; Path=").append(path);
builder.append("\n");
if (WILL_NOT_BE_SENT.equals(path)) {
// Because it wasn't sent and thus wasn't deleted.
--unexpectedSessionCookies;
} else if (JSESSIONID.equals(cookie.getName())) {
// Because this test harness isn't winstone and the cleaning
// code is only responsible for deleting "JSESSIONID." cookies.
--unexpectedSessionCookies;
}
}
}
System.err.println(builder.toString());
assertThat(unexpectedSessionCookies, is(0));
}
}
......@@ -8,6 +8,7 @@ import java.util.Arrays;
import static java.util.logging.Level.FINEST;
import java.util.stream.Collectors;
import hudson.model.User;
import jenkins.model.Jenkins;
import jenkins.security.seed.UserSeedProperty;
......@@ -29,8 +30,11 @@ import org.jvnet.hudson.test.For;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.LoggerRule;
import org.kohsuke.stapler.Stapler;
import org.springframework.dao.DataAccessException;
import test.security.realm.InMemorySecurityRealm;
import javax.annotation.concurrent.GuardedBy;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.is;
......@@ -289,6 +293,51 @@ public class TokenBasedRememberMeServices2Test {
}
}
@Test
@Issue("JENKINS-56243")
public void rememberMeToken_shouldLoadUserDetailsOnlyOnce() throws Exception {
j.jenkins.setDisableRememberMe(false);
LoadUserCountingSecurityRealm realm = new LoadUserCountingSecurityRealm();
realm.createAccount("alice");
j.jenkins.setSecurityRealm(realm);
User alice = User.getOrCreateByIdOrFullName("alice");
realm.verifyInvocations(1);
// first, start a session with a remember me token
Cookie cookie = getRememberMeCookie(j.createWebClient().login("alice", "alice", true));
// next, start a new session with that token
JenkinsRule.WebClient wc = j.createWebClient();
wc.getCookieManager().addCookie(cookie);
// trigger remember me
String sessionSeed = wc.executeOnServer(() -> Stapler.getCurrentRequest().getSession(false).getAttribute(UserSeedProperty.USER_SESSION_SEED).toString());
realm.verifyInvocations(1);
String userSeed = alice.getProperty(UserSeedProperty.class).getSeed();
assertEquals(userSeed, sessionSeed);
// finally, ensure that loadUserByUsername is not being called anymore
wc.goTo("");
assertUserConnected(wc, "alice");
realm.verifyInvocations(0);
}
private static class LoadUserCountingSecurityRealm extends InMemorySecurityRealm {
// if this class wasn't serialized into config.xml, this could be replaced by @Spy from Mockito
@GuardedBy("this")
private int counter = 0;
@Override
public synchronized UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
++counter;
return super.loadUserByUsername(username);
}
synchronized void verifyInvocations(int count) {
assertEquals(count, counter);
counter = 0;
}
}
private Cookie createRememberMeCookie(TokenBasedRememberMeServices2 tokenService, long deltaDuration, hudson.model.User user) throws Exception {
long tokenValiditySeconds = tokenService.getTokenValiditySeconds();
long expiryTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(tokenValiditySeconds);
......
......@@ -27,7 +27,6 @@ package jenkins.install;
import hudson.ClassicPluginStrategy;
import jenkins.plugins.DetachedPluginsUtil;
import jenkins.plugins.DetachedPluginsUtil.DetachedPlugin;
import hudson.Plugin;
import hudson.PluginManager;
import hudson.PluginManagerUtil;
import hudson.PluginWrapper;
......@@ -87,62 +86,6 @@ public class LoadDetachedPluginsTest {
});
}
@Issue("JENKINS-48604")
@Test
@LocalData
public void upgradeFromJenkins2WithNewerDependency() {
VersionNumber since = new VersionNumber("2.0");
rr.then(r -> {
List<DetachedPlugin> detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(since);
assertThat("Plugins have been detached since the pre-upgrade version",
detachedPlugins.size(), greaterThan(1));
assertThat("Plugins detached between the pre-upgrade version and the current version should be installed",
getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size()));
Plugin scriptSecurity = r.jenkins.getPlugin("script-security");
assertThat("Script-security should be installed", scriptSecurity, notNullValue());
assertThat("Dependencies of detached plugins should not be downgraded",
scriptSecurity.getWrapper().getVersionNumber(), equalTo(new VersionNumber("1.34")));
assertNoFailedPlugins(r);
});
}
@Test
@LocalData
public void upgradeFromJenkins2WithOlderDependency() {
VersionNumber since = new VersionNumber("2.0");
rr.then(r -> {
List<DetachedPlugin> detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(since);
assertThat("Plugins have been detached since the pre-upgrade version",
detachedPlugins.size(), greaterThan(1));
assertThat("Plugins detached between the pre-upgrade version and the current version should be installed",
getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size()));
Plugin scriptSecurity = r.jenkins.getPlugin("script-security");
assertThat("Script-security should be installed", scriptSecurity, notNullValue());
assertThat("Dependencies of detached plugins should be upgraded to the required version",
scriptSecurity.getWrapper().getVersionNumber(), equalTo(new VersionNumber("1.56")));
assertNoFailedPlugins(r);
});
}
@Issue("JENKINS-48899")
@Test
@LocalData
public void upgradeFromJenkins2WithNewerPlugin() {
// @LocalData has command-launcher 1.2 installed, which should not be downgraded to the detached version: 1.0.
VersionNumber since = new VersionNumber("2.0");
rr.then(r -> {
List<DetachedPlugin> detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(since);
assertThat("Plugins have been detached since the pre-upgrade version",
detachedPlugins.size(), greaterThan(1));
assertThat("Plugins detached between the pre-upgrade version and the current version should be installed",
getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size()));
Plugin commandLauncher = r.jenkins.getPlugin("command-launcher");
assertThat("Installed detached plugins should not be overwritten by older versions",
commandLauncher.getWrapper().getVersionNumber(), equalTo(new VersionNumber("1.2")));
assertNoFailedPlugins(r);
});
}
@Test
public void newInstallation() {
rr.then(r -> {
......
package jenkins.model;
import hudson.Functions;
import hudson.Util;
import hudson.init.InitMilestone;
import hudson.maven.MavenModuleSet;
import hudson.maven.MavenModuleSetBuild;
......@@ -21,12 +20,10 @@ import org.jvnet.hudson.test.RestartableJenkinsRule;
import org.jvnet.hudson.test.recipes.LocalData;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.stream.Stream;
import java.util.stream.Collectors;
......@@ -353,45 +350,4 @@ public class JenkinsBuildsAndWorkspacesDirectoriesTest {
});
}
@Test
@Issue("JENKINS-17137")
public void externalBuildDirectorySymlinks() throws Exception {
assumeFalse(Functions.isWindows()); // symlinks may not be available
// Hack to get String builds usable in lambda below
final List<String> builds = new ArrayList<>();
story.then(steps -> {
builds.add(story.j.createTmpDir().toString());
setBuildsDirProperty(builds.get(0) + "/${ITEM_FULL_NAME}");
});
story.then(steps -> {
assertEquals(builds.get(0) + "/${ITEM_FULL_NAME}", story.j.jenkins.getRawBuildsDir());
FreeStyleProject p = story.j.jenkins.createProject(MockFolder.class, "d").createProject(FreeStyleProject.class, "p");
FreeStyleBuild b1 = p.scheduleBuild2(0).get();
File link = new File(p.getRootDir(), "lastStable");
assertTrue(link.exists());
assertEquals(resolveAll(link).getAbsolutePath(), b1.getRootDir().getAbsolutePath());
FreeStyleBuild b2 = p.scheduleBuild2(0).get();
assertTrue(link.exists());
assertEquals(resolveAll(link).getAbsolutePath(), b2.getRootDir().getAbsolutePath());
b2.delete();
assertTrue(link.exists());
assertEquals(resolveAll(link).getAbsolutePath(), b1.getRootDir().getAbsolutePath());
b1.delete();
assertFalse(link.exists());
});
}
private File resolveAll(File link) throws InterruptedException, IOException {
while (true) {
File f = Util.resolveSymlinkToFile(link);
if (f == null) {
return link;
}
link = f;
}
}
}
package jenkins.model;
import hudson.Functions;
import hudson.Util;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Job;
import hudson.model.Run;
import java.io.File;
import java.nio.file.Files;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.FailureBuilder;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
public class PeepholePermalinkTest {
......@@ -24,76 +22,45 @@ public class PeepholePermalinkTest {
*/
@Test
public void basics() throws Exception {
Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows());
FreeStyleProject p = j.createFreeStyleProject();
FreeStyleBuild b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
File lsb = new File(p.getBuildDir(), "lastSuccessfulBuild");
File lfb = new File(p.getBuildDir(), "lastFailedBuild");
String lsb = "lastSuccessfulBuild";
String lfb = "lastFailedBuild";
assertLink(lsb, b1);
assertStorage(lsb, p, b1);
// now another build that fails
p.getBuildersList().add(new FailureBuilder());
FreeStyleBuild b2 = p.scheduleBuild2(0).get();
assertLink(lsb, b1);
assertLink(lfb, b2);
assertStorage(lsb, p, b1);
assertStorage(lfb, p, b2);
// one more build and this time it succeeds
p.getBuildersList().clear();
FreeStyleBuild b3 = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
assertLink(lsb, b3);
assertLink(lfb, b2);
assertStorage(lsb, p, b3);
assertStorage(lfb, p, b2);
// delete b3 and symlinks should update properly
// delete b3 and links should update properly
b3.delete();
assertLink(lsb, b1);
assertLink(lfb, b2);
assertStorage(lsb, p, b1);
assertStorage(lfb, p, b2);
b1.delete();
assertLink(lsb, null);
assertLink(lfb, b2);
assertStorage(lsb, p, null);
assertStorage(lfb, p, b2);
b2.delete();
assertLink(lsb, null);
assertLink(lfb, null);
}
private void assertLink(File symlink, Run build) throws Exception {
assertEquals(build == null ? "-1" : Integer.toString(build.getNumber()), Util.resolveSymlink(symlink));
}
/**
* job/JOBNAME/lastStable and job/JOBNAME/lastSuccessful symlinks that we
* used to generate should still work
*/
@Test
public void legacyCompatibility() throws Exception {
Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows());
FreeStyleProject p = j.createFreeStyleProject();
FreeStyleBuild b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
for (String n : new String[] {"lastStable", "lastSuccessful"}) {
// test if they both point to b1
assertEquals(new File(p.getRootDir(), n + "/build.xml").length(), new File(b1.getRootDir(), "build.xml").length());
}
assertStorage(lsb, p, null);
assertStorage(lfb, p, null);
}
@Test
@Issue("JENKINS-19034")
public void rebuildBuildNumberPermalinks() throws Exception {
FreeStyleProject p = j.createFreeStyleProject();
FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0));
File f = new File(p.getBuildDir(), "1");
// assertTrue(Util.isSymlink(f))
f.delete();
PeepholePermalink link = (PeepholePermalink) p.getPermalinks().stream().filter(l -> l instanceof PeepholePermalink).findAny().get();
link.updateCache(p, b);
assertTrue("build symlink hasn't been restored", f.exists());
private void assertStorage(String id, Job<?, ?> job, Run<?, ?> build) throws Exception {
assertThat(Files.readAllLines(PeepholePermalink.storageFor(job.getBuildDir()).toPath()),
hasItem(id + " " + (build == null ? -1 : build.getNumber())));
}
}
/*
* The MIT License
*
* Copyright (c) 2019 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.telemetry;
import hudson.ExtensionList;
import hudson.model.UnprotectedRootAction;
import hudson.security.csrf.CrumbExclusion;
import jenkins.telemetry.impl.java11.CatcherClassLoader;
import jenkins.telemetry.impl.java11.MissingClassTelemetry;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.annotation.CheckForNull;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.assertEquals;
/**
* This test needs to be here to be able to modify the {@link Telemetry#ENDPOINT} as it's package protected.
*/
public class MissingClassTelemetryTest {
private static final String TELEMETRY_ENDPOINT = "uplink";
private CatcherClassLoader cl;
@Rule
public JenkinsRule j = new JenkinsRule();
private static JSONObject received = null;
@Before
public void prepare() throws Exception {
received = null;
cl = new CatcherClassLoader(this.getClass().getClassLoader());
Telemetry.ENDPOINT = j.getURL().toString() + TELEMETRY_ENDPOINT + "/events";
j.jenkins.setNoUsageStatistics(false); // tests usually don't submit this, but we need this
}
/**
* Test if the telemetry sent works and the received data is the expected for a specific case (5 occurrences of the
* same stack trace).
* @throws InterruptedException if the thread is interrupted while sleeping
*/
@Test
public void telemetrySentWorks() throws InterruptedException {
Assume.assumeTrue("The telemetry should be enabled", MissingClassTelemetry.enabled());
// Generate 5 events
for(int i = 0; i < 5; i++) {
try {
cl.loadClass("sun.java.MyNonExistentClass");
} catch (ClassNotFoundException ignored) {
}
}
// Run the telemetry sent
ExtensionList.lookupSingleton(Telemetry.TelemetryReporter.class).doRun();
do {
Thread.sleep(250);
} while (received == null); // this might end up being flaky due to 1 to many active telemetry trials
// The telemetry stuff sent is the class expected, the number of events is 1, the class not found is the
// expected and the number of occurrences is the expected
assertEquals(MissingClassTelemetry.class.getName(), received.getString("type"));
JSONArray events = received.getJSONObject("payload").getJSONArray("classMissingEvents");
assertEquals(1, events.size());
assertEquals("sun.java.MyNonExistentClass", ((JSONObject) events.get(0)).get("className"));
assertEquals(5, Integer.parseInt( (String) ((JSONObject) events.get(0)).get("occurrences")));
}
/**
* Avoid crumb checking (CSRF)
*/
@TestExtension
public static class NoCrumb extends CrumbExclusion {
@Override
public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String pathInfo = request.getPathInfo();
if (pathInfo != null && pathInfo.startsWith("/uplink")) {
chain.doFilter(request, response);
return true;
}
return false;
}
}
@TestExtension
public static class TelemetryReceiver implements UnprotectedRootAction {
public void doEvents(StaplerRequest request, StaplerResponse response) throws IOException {
StringWriter sw = new StringWriter();
IOUtils.copy(request.getInputStream(), sw, StandardCharsets.UTF_8);
received = JSONObject.fromObject(sw.toString());
}
@CheckForNull
@Override
public String getIconFileName() {
return null;
}
@CheckForNull
@Override
public String getDisplayName() {
return null;
}
@CheckForNull
@Override
public String getUrlName() {
return TELEMETRY_ENDPOINT;
}
}
}
/*
* The MIT License
*
* Copyright (c) 2019 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.telemetry.impl.java11;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.LoggerRule;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/**
* Tests without a running Jenkins for Java11 Telemetry of ClassNotFoundException.
*/
public class MissingClassTelemetryFasterTest {
private CatcherClassLoader cl;
@Rule
public LoggerRule logging = new LoggerRule();
@Before
public void cleanEvents() {
cl = new CatcherClassLoader(this.getClass().getClassLoader());
}
@Test
public void maxNumberEvents() {
Assume.assumeTrue("The telemetry should be enabled", MissingClassTelemetry.enabled());
// Backup to restore at the end of the test
int maxEventsBefore = MissingClassEvents.MAX_EVENTS_PER_SEND;
try {
MissingClassEvents.MAX_EVENTS_PER_SEND = 1;
try {
cl.loadClass("sun.java.MyNonExistentClass");
} catch (ClassNotFoundException ignored) {
}
try {
cl.loadClass("sun.java.MyNonExistentJavaClass");
} catch (ClassNotFoundException ignored) {
}
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> eventsGathered = MissingClassTelemetry.getEvents().getEventsAndClean();
// Only one class miss gathered with two occurrences
assertEquals(1, eventsGathered.size());
} finally {
MissingClassEvents.MAX_EVENTS_PER_SEND = maxEventsBefore;
}
}
/**
* The same class failed to be loaded in different places ends up in two records of telemetry with one occurrence
* each.
*/
@Test
public void differentEventsAlthoughSameClass() {
Assume.assumeTrue("The telemetry should be enabled", MissingClassTelemetry.enabled());
try {
cl.loadClass("sun.java.MyNonExistentClass");
} catch (ClassNotFoundException ignored) {
}
try {
cl.loadClass("sun.java.MyNonExistentJavaClass");
} catch (ClassNotFoundException ignored) {
}
// Get the events gathered
MissingClassEvents events = MissingClassTelemetry.getEvents();
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> eventsGathered = events.getEventsAndClean();
// Only one class miss gathered with two occurrences
assertEquals(2, eventsGathered.size());
assertEquals(1, eventsGathered.values().iterator().next().getOccurrences());
assertEquals(1, eventsGathered.values().iterator().next().getOccurrences());
}
/**
* The same class thrown in the same line ends up in a single event with two occurrences.
*/
@Test
public void addOccurrenceIfSameStackTrace() {
Assume.assumeTrue("The telemetry should be enabled", MissingClassTelemetry.enabled());
for (int i = 0; i < 2; i++) {
try {
//Exceptions thrown at the same line, with the same stack trace become occurrences of just one event
cl.loadClass("sun.java.MyNonExistentJavaClass");
} catch (ClassNotFoundException ignored) {
}
}
// Get the events gathered
MissingClassEvents events = MissingClassTelemetry.getEvents();
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> eventsGathered = events.getEventsAndClean();
// Only one class miss gathered with two occurrences
assertEquals(1, eventsGathered.size());
assertEquals(2, eventsGathered.values().iterator().next().getOccurrences());
}
/**
* A class not from the split packages is not gathered.
*/
@Test
public void nonJavaClassesNotGathered() {
Assume.assumeTrue("The telemetry should be enabled", MissingClassTelemetry.enabled());
try {
cl.loadClass("jenkins.MyNonExistentClass");
} catch (ClassNotFoundException ignored) {
}
// Get the events gathered
MissingClassEvents events = MissingClassTelemetry.getEvents();
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> eventsGathered = events.getEventsAndClean();
// No events gathered
assertEquals(0, eventsGathered.size());
}
/**
* Only a max number of events is gathered. In this test, just one wit two occurrences
*/
@Test
public void maxEventsLimitedSameStackTrace() {
Assume.assumeTrue("The telemetry should be enabled", MissingClassTelemetry.enabled());
MissingClassEvents.MAX_EVENTS_PER_SEND = 1;
for (int i = 0; i < 2; i++) {
try {
//Exceptions thrown at the same line, with the same stack trace become occurrences of just one event
cl.loadClass("sun.java.MyNonExistentJavaClass");
} catch (ClassNotFoundException ignored) {
}
}
// Get the events gathered
MissingClassEvents events = MissingClassTelemetry.getEvents();
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> eventsGathered = events.getEventsAndClean();
// Only one event gathered
assertEquals(1, eventsGathered.size());
assertEquals(2, eventsGathered.values().iterator().next().getOccurrences());
}
/**
* Only a max number of events is gathered. In this test, just one wit one occurrence. The second one is discarded
*/
@Test
public void maxEventsLimitedDifferentStackTrace() {
Assume.assumeTrue("The telemetry should be enabled", MissingClassTelemetry.enabled());
MissingClassEvents.MAX_EVENTS_PER_SEND = 1;
try {
cl.loadClass("sun.java.MyNonExistentClassGathered");
} catch (ClassNotFoundException ignored) {
}
try {
cl.loadClass("sun.java.MyNonExistentJavaClassNotGathered");
} catch (ClassNotFoundException ignored) {
}
// Get the events gathered
MissingClassEvents events = MissingClassTelemetry.getEvents();
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> eventsGathered = events.getEventsAndClean();
// Only one event gathered
assertEquals(1, eventsGathered.size());
assertEquals(1, eventsGathered.values().iterator().next().getOccurrences());
assertThat(eventsGathered.values().iterator().next().getStackTrace(), containsString("MyNonExistentClassGathered"));
assertThat(eventsGathered.values().iterator().next().getStackTrace(), not(containsString("MyNonExistentJavaClassNotGathered")));
}
/**
* Test the cycles in the exceptions. This specific tests shows that we first look for reportable exceptions in the
* causes and when found a reportable exception, we stop searching. So the warning because a cycle is not logged.
*/
@Test
public void cyclesNotReachedBecauseCNFEReported() {
Assume.assumeTrue("The telemetry should be enabled", MissingClassTelemetry.enabled());
logging.record(MissingClassTelemetry.class, Logger.getLogger(MissingClassTelemetry.class.getName()).getLevel()).capture(5);
try {
/*
parent -> child -> cnfe
\
parent
We first look into the causes exceptions. When found, we don't look into the suppressed, so the cycle is not
found here
*/
ClassNotFoundException cnfe = new ClassNotFoundException("sun.java.MyNonExistentClassGathered");
Exception child = new Exception("child", cnfe);
Exception parent = new Exception("parent", child); // parent -> caused by -> child
child.addSuppressed(parent);
// Some extra wrapping
throw new Exception(new Exception (new Exception (parent)));
} catch (Exception e) {
// Look for anything to report
MissingClassTelemetry.reportExceptionInside(e);
// Get the events gathered
MissingClassEvents events = MissingClassTelemetry.getEvents();
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> eventsGathered = events.getEventsAndClean();
// One event gathered
assertEquals(1, eventsGathered.size());
// the circular reference has not been recorded in the log because we reached a CNFE previously
assertEquals("No circular message was printed in logs", 0, logging.getRecords().stream().filter(r -> r.getMessage().contains(MissingClassTelemetry.CIRCULAR_REFERENCE)).count());
}
}
/**
* Test the cycles in the exceptions. This specific tests shows that we first look for reportable exceptions deep
* in the causes and suppressed exceptions in this order. We report the cycle but also the CNFE.
*/
@Test
public void cnfeFoundAfterCycle() {
Assume.assumeTrue("The telemetry should be enabled", MissingClassTelemetry.enabled());
logging.record(MissingClassTelemetry.class, Logger.getLogger(MissingClassTelemetry.class.getName()).getLevel()).capture(5);
try {
ClassNotFoundException cnfe = new ClassNotFoundException("sun.java.MyNonExistentClassGathered");
/*
parent -> child
\ \
cnfe parent
*/
Exception child = new Exception("child");
Exception parent = new Exception("parent", child); // parent -> caused by -> child
child.addSuppressed(parent); // Boooomm!!!! The parent is a child suppressed exception -> cycle
parent.addSuppressed(cnfe);
// Some extra wrapping
throw new Exception(new Exception (new Exception (parent)));
} catch (Exception e) {
// Look for anything to report
MissingClassTelemetry.reportExceptionInside(e);
// Get the events gathered
MissingClassEvents events = MissingClassTelemetry.getEvents();
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> eventsGathered = events.getEventsAndClean();
// One event gathered
assertEquals(1, eventsGathered.size());
// the circular reference has been recorded in the log
assertThat(logging, LoggerRule.recorded(containsString(MissingClassTelemetry.CIRCULAR_REFERENCE)));
}
}
/**
* Test the cycles in the exceptions. This specific tests shows that we first look for reportable exceptions deep
* in the causes and suppressed exceptions in this order. We report the cycle but also the CNFE in the parent.
*/
@Test
public void cnfeAfterCNFENotJava11AndCycle() {
Assume.assumeTrue("The telemetry should be enabled", MissingClassTelemetry.enabled());
logging.record(MissingClassTelemetry.class, Logger.getLogger(MissingClassTelemetry.class.getName()).getLevel()).capture(5);
try {
ClassNotFoundException cnfe = new ClassNotFoundException("sun.java.MyNonExistentClassGathered");
ClassNotFoundException cnfeNonJava11 = new ClassNotFoundException("MyNonExistentClassGathered");
/*
parent -> child -> grandchild
\ \ \
cnfe parent cnfe (non java11)
*/
Exception grandchild = new Exception("grandchild");
Exception child = new Exception("child");
Exception parent = new Exception("parent", child); // parent -> caused by -> child
child.addSuppressed(parent); // Boooomm!!!! The parent is a child suppressed exception -> cycle
grandchild.addSuppressed(cnfeNonJava11);
parent.addSuppressed(cnfe);
// Some extra wrapping
throw new Exception(new Exception (new Exception (parent)));
} catch (Exception e) {
// Look for anything to report
MissingClassTelemetry.reportExceptionInside(e);
// Get the events gathered
MissingClassEvents events = MissingClassTelemetry.getEvents();
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> eventsGathered = events.getEventsAndClean();
// One event gathered
assertEquals(1, eventsGathered.size());
// the circular reference has been recorded in the log
assertThat(logging, LoggerRule.recorded(containsString(MissingClassTelemetry.CIRCULAR_REFERENCE)));
}
}
@Test
public void nothingGatheredWhenTelemetryDisabled() {
Assume.assumeFalse("The telemetry should not be enabled", MissingClassTelemetry.enabled());
try {
cl.loadClass("sun.java.MyNonExistentClass");
} catch (ClassNotFoundException ignored) {
}
ConcurrentHashMap<List<StackTraceElement>, MissingClassEvent> eventsGathered = MissingClassTelemetry.getEvents().getEventsAndClean();
// No events gathered
assertEquals(0, eventsGathered.size());
}
}
......@@ -405,7 +405,7 @@ THE SOFTWARE.
<artifactItem>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>command-launcher</artifactId>
<version>1.0</version>
<version>1.2</version>
<type>hpi</type>
</artifactItem>
<artifactItem>
......@@ -420,6 +420,12 @@ THE SOFTWARE.
<version>2.3.0</version>
<type>hpi</type>
</artifactItem>
<artifactItem>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>trilead-api</artifactId>
<version>1.0.4</version>
<type>hpi</type>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/detached-plugins</outputDirectory>
<stripVersion>true</stripVersion>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册