提交 6d2de7d9 编写于 作者: J Jesse Glick

Merge branch 'master' into CommandLauncher-JENKINS-47393

......@@ -73,7 +73,7 @@ It is a good practice is to create branches instead of pushing to master.
Once your Pull Request is ready to be merged,
the repository maintainers will integrate it, prepare changelogs and
ensure it gets released in one of incoming Weekly releases.
There is no additinal action required from pull request authors at this point.
There is no additional action required from pull request authors at this point.
## Copyright
......@@ -85,7 +85,7 @@ Contributions under MIT-compatible licenses may be also rejected if they are not
We **Do NOT** require pull request submitters to sign the [contributor agreement](https://wiki.jenkins.io/display/JENKINS/Copyright+on+source+code)
as long as the code is licensed under MIT and merged by one of the contributors with the signed agreement.
We still encourage people to sign the contributor agreement if they intend to submit more than a few pull ruquests.
We still encourage people to sign the contributor agreement if they intend to submit more than a few pull requests.
Signing is also a mandatory prerequisite for getting merge/push permissions to core repositories
and for joining teams like [Jenkins Security Team](https://jenkins.io/security/#team).
......@@ -101,8 +101,8 @@ just submit a pull request.
# Links
* [Jenkins Contribution Landing Page](https://jenkins.io/paricipate/)
* [Jenkisn IRC Channel](https://jenkins.io/chat/)
* [Jenkins Contribution Landing Page](https://jenkins.io/participate/)
* [Jenkins IRC Channel](https://jenkins.io/chat/)
* [Beginners Guide To Contributing](https://wiki.jenkins.io/display/JENKINS/Beginners+Guide+to+Contributing)
* [List of newbie-friendly issues in the core](https://issues.jenkins-ci.org/issues/?jql=project%20%3D%20JENKINS%20AND%20status%20in%20(Open%2C%20%22In%20Progress%22%2C%20Reopened)%20AND%20component%20%3D%20core%20AND%20labels%20in%20(newbie-friendly))
......
......@@ -5,7 +5,7 @@
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>2.85-SNAPSHOT</version>
<version>2.86-SNAPSHOT</version>
</parent>
<artifactId>cli</artifactId>
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>2.85-SNAPSHOT</version>
<version>2.86-SNAPSHOT</version>
</parent>
<artifactId>jenkins-core</artifactId>
......
......@@ -106,7 +106,6 @@ import jenkins.SoloFilePathFilter;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import jenkins.util.ContextResettingExecutorService;
import jenkins.util.SystemProperties;
import jenkins.util.VirtualFile;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
......@@ -2371,7 +2370,7 @@ public final class FilePath implements Serializable {
* Default bound for {@link #validateAntFileMask(String, int, boolean)}.
* @since 1.592
*/
public static int VALIDATE_ANT_FILE_MASK_BOUND = SystemProperties.getInteger(FilePath.class.getName() + ".VALIDATE_ANT_FILE_MASK_BOUND", 10000);
public static int VALIDATE_ANT_FILE_MASK_BOUND = Integer.getInteger(FilePath.class.getName() + ".VALIDATE_ANT_FILE_MASK_BOUND", 10000);
/**
* Like {@link #validateAntFileMask(String)} but performing only a bounded number of operations.
......
......@@ -2043,4 +2043,13 @@ public class Functions {
}
}
@Restricted(NoExternalUse.class) // for cc.xml.jelly
public static Collection<TopLevelItem> getCCItems(View v) {
if (Stapler.getCurrentRequest().getParameter("recursive") != null) {
return Items.getAllItems(v.getOwner().getItemGroup(), TopLevelItem.class);
} else {
return v.getItems();
}
}
}
......@@ -30,6 +30,8 @@ import java.io.Writer;
import java.nio.charset.Charset;
import java.security.interfaces.RSAPublicKey;
import javax.annotation.Nullable;
import hudson.model.AperiodicWork;
import jenkins.model.Jenkins;
import jenkins.model.identity.InstanceIdentityProvider;
import jenkins.util.SystemProperties;
......@@ -53,6 +55,7 @@ import java.net.Socket;
import java.nio.channels.ServerSocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
......@@ -98,6 +101,11 @@ public final class TcpSlaveAgentListener extends Thread {
throw (BindException)new BindException("Failed to listen on port "+port+" because it's already in use.").initCause(e);
}
this.configuredPort = port;
setUncaughtExceptionHandler((t, e) -> {
LOGGER.log(Level.SEVERE, "Uncaught exception in TcpSlaveAgentListener " + t + ", attempting to reschedule thread", e);
shutdown();
TcpSlaveAgentListenerRescheduler.schedule(t, e);
});
LOGGER.log(Level.FINE, "TCP agent listener started on port {0}", getPort());
......@@ -155,7 +163,14 @@ public final class TcpSlaveAgentListener extends Thread {
// we take care of buffering on our own
s.setTcpNoDelay(true);
new ConnectionHandler(s).start();
new ConnectionHandler(s, new ConnectionHandlerFailureCallback(this) {
@Override
public void run(Throwable cause) {
LOGGER.log(Level.WARNING, "Connection handler failed, restarting listener", cause);
shutdown();
TcpSlaveAgentListenerRescheduler.schedule(getParentThread(), cause);
}
}).start();
}
} catch (IOException e) {
if(!shuttingDown) {
......@@ -194,12 +209,21 @@ public final class TcpSlaveAgentListener extends Thread {
*/
private final int id;
public ConnectionHandler(Socket s) {
public ConnectionHandler(Socket s, ConnectionHandlerFailureCallback parentTerminator) {
this.s = s;
synchronized(getClass()) {
id = iotaGen++;
}
setName("TCP agent connection handler #"+id+" with "+s.getRemoteSocketAddress());
setUncaughtExceptionHandler((t, e) -> {
LOGGER.log(Level.SEVERE, "Uncaught exception in TcpSlaveAgentListener ConnectionHandler " + t, e);
try {
s.close();
parentTerminator.run(e);
} catch (IOException e1) {
LOGGER.log(Level.WARNING, "Could not close socket after unexpected thread death", e1);
}
});
}
@Override
......@@ -301,6 +325,21 @@ public final class TcpSlaveAgentListener extends Thread {
}
}
// This is essentially just to be able to pass the parent thread into the callback, as it can't access it otherwise
private abstract class ConnectionHandlerFailureCallback {
private Thread parentThread;
public ConnectionHandlerFailureCallback(Thread parentThread) {
this.parentThread = parentThread;
}
public Thread getParentThread() {
return parentThread;
}
public abstract void run(Throwable cause);
}
/**
* This extension provides a Ping protocol that allows people to verify that the TcpSlaveAgentListener is alive.
* We also use this to wake the acceptor thread on termination.
......@@ -383,6 +422,84 @@ public final class TcpSlaveAgentListener extends Thread {
}
}
/**
* Reschedules the <code>TcpSlaveAgentListener</code> on demand. Disables itself after running.
*/
@Extension
@Restricted(NoExternalUse.class)
public static class TcpSlaveAgentListenerRescheduler extends AperiodicWork {
private Thread originThread;
private Throwable cause;
private long recurrencePeriod = 5000;
private boolean isActive;
public TcpSlaveAgentListenerRescheduler() {
isActive = false;
}
public TcpSlaveAgentListenerRescheduler(Thread originThread, Throwable cause) {
this.originThread = originThread;
this.cause = cause;
this.isActive = false;
}
public void setOriginThread(Thread originThread) {
this.originThread = originThread;
}
public void setCause(Throwable cause) {
this.cause = cause;
}
public void setActive(boolean active) {
isActive = active;
}
@Override
public long getRecurrencePeriod() {
return recurrencePeriod;
}
@Override
public AperiodicWork getNewInstance() {
return new TcpSlaveAgentListenerRescheduler(originThread, cause);
}
@Override
protected void doAperiodicRun() {
if (isActive) {
try {
if (originThread.isAlive()) {
originThread.interrupt();
}
int port = Jenkins.getInstance().getSlaveAgentPort();
if (port != -1) {
new TcpSlaveAgentListener(port).start();
LOGGER.log(Level.INFO, "Restarted TcpSlaveAgentListener");
} else {
LOGGER.log(Level.SEVERE, "Uncaught exception in TcpSlaveAgentListener " + originThread + ". Port is disabled, not rescheduling", cause);
}
isActive = false;
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not reschedule TcpSlaveAgentListener - trying again.", cause);
}
}
}
public static void schedule(Thread originThread, Throwable cause) {
schedule(originThread, cause,5000);
}
public static void schedule(Thread originThread, Throwable cause, long approxDelay) {
TcpSlaveAgentListenerRescheduler rescheduler = AperiodicWork.all().get(TcpSlaveAgentListenerRescheduler.class);
rescheduler.originThread = originThread;
rescheduler.cause = cause;
rescheduler.recurrencePeriod = approxDelay;
rescheduler.isActive = true;
}
}
/**
* Connection terminated because we are reconnected from the current peer.
*/
......
......@@ -456,7 +456,7 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
if (node instanceof Jenkins) {
listener.getLogger().print(Messages.AbstractBuild_BuildingOnMaster());
} else {
listener.getLogger().print(Messages.AbstractBuild_BuildingRemotely(ModelHyperlinkNote.encodeTo("/computer/" + builtOn, builtOn)));
listener.getLogger().print(Messages.AbstractBuild_BuildingRemotely(ModelHyperlinkNote.encodeTo("/computer/" + builtOn, node.getDisplayName())));
Set<LabelAtom> assignedLabels = new HashSet<LabelAtom>(node.getAssignedLabels());
assignedLabels.remove(node.getSelfLabel());
if (!assignedLabels.isEmpty()) {
......
......@@ -29,6 +29,8 @@ import hudson.Util;
import hudson.diagnosis.OldDataMonitor;
import hudson.model.Descriptor.FormException;
import hudson.model.listeners.ItemListener;
import hudson.search.CollectionSearchIndex;
import hudson.search.SearchIndexBuilder;
import hudson.security.ACL;
import hudson.security.ACLContext;
import hudson.util.CaseInsensitiveComparator;
......@@ -168,7 +170,8 @@ public class ListView extends View implements DirectlyModifiableView {
public DescribableList<ListViewColumn, Descriptor<ListViewColumn>> getColumns() {
return columns;
}
/**
* Returns a read-only view of all {@link Job}s in this view.
*
......@@ -178,6 +181,20 @@ public class ListView extends View implements DirectlyModifiableView {
*/
@Override
public List<TopLevelItem> getItems() {
return getItems(this.recurse);
}
/**
* Returns a read-only view of all {@link Job}s in this view.
*
*
* <p>
* This method returns a separate copy each time to avoid
* concurrent modification issue.
* @param recurse {@code false} not to recurse in ItemGroups
* true to recurse in ItemGroups
*/
private List<TopLevelItem> getItems(boolean recurse) {
SortedSet<String> names;
List<TopLevelItem> items = new ArrayList<TopLevelItem>();
......@@ -217,6 +234,23 @@ public class ListView extends View implements DirectlyModifiableView {
return items;
}
@Override
public SearchIndexBuilder makeSearchIndex() {
SearchIndexBuilder sib = new SearchIndexBuilder().addAllAnnotations(this);
sib.add(new CollectionSearchIndex<TopLevelItem>() {// for jobs in the view
protected TopLevelItem get(String key) { return getItem(key); }
protected Collection<TopLevelItem> all() { return getItems(); }
@Override
protected String getName(TopLevelItem o) {
// return the name instead of the display for suggestion searching
return o.getName();
}
});
// add the display name for each item in the search index
addDisplayNamesToSearchIndex(sib, getItems(true));
return sib;
}
private List<TopLevelItem> expand(Collection<TopLevelItem> items, List<TopLevelItem> allItems) {
for (TopLevelItem item : items) {
if (item instanceof ItemGroup) {
......
......@@ -1175,28 +1175,63 @@ public class Queue extends ResourceController implements Saveable {
/**
* Checks if the given item should be prevented from entering into the {@link #buildables} state
* and instead stay in the {@link #blockedProjects} state.
*
* @return the reason of blockage if it exists null otherwise.
*/
private boolean isBuildBlocked(Item i) {
if (i.task.isBuildBlocked() || !canRun(i.task.getResourceList()))
return true;
@CheckForNull
private CauseOfBlockage getCauseOfBlockageForItem(Item i) {
CauseOfBlockage causeOfBlockage = getCauseOfBlockageForTask(i.task);
if (causeOfBlockage != null) {
return causeOfBlockage;
}
for (QueueTaskDispatcher d : QueueTaskDispatcher.all()) {
if (d.canRun(i)!=null)
return true;
causeOfBlockage = d.canRun(i);
if (causeOfBlockage != null)
return causeOfBlockage;
}
if(!(i instanceof BuildableItem)) {
// Make sure we don't queue two tasks of the same project to be built
// unless that project allows concurrent builds. Once item is buildable it's ok.
//
// This check should never pass. And must be remove once we can completely rely on `getCauseOfBlockage`.
// If `task.isConcurrentBuild` returns `false`,
// it should also return non-null value for `task.getCauseOfBlockage` in case of on-going execution.
// But both are public non-final methods, so, we need to keep backward compatibility here.
// And check one more time across all `buildables` and `pendings` for O(N) each.
if (!i.task.isConcurrentBuild() && (buildables.containsKey(i.task) || pendings.containsKey(i.task))) {
return CauseOfBlockage.fromMessage(Messages._Queue_InProgress());
}
}
return false;
return null;
}
/**
* Make sure we don't queue two tasks of the same project to be built
* unless that project allows concurrent builds.
*
* Checks if the given task knows the reasons to be blocked or it needs some unavailable resources
*
* @param task the task.
* @return the reason of blockage if it exists null otherwise.
*/
private boolean allowNewBuildableTask(Task t) {
if (t.isConcurrentBuild()) {
return true;
@CheckForNull
private CauseOfBlockage getCauseOfBlockageForTask(Task task) {
CauseOfBlockage causeOfBlockage = task.getCauseOfBlockage();
if (causeOfBlockage != null) {
return task.getCauseOfBlockage();
}
return !buildables.containsKey(t) && !pendings.containsKey(t);
if (!canRun(task.getResourceList())) {
ResourceActivity r = getBlockingActivity(task);
if (r != null) {
if (r == task) // blocked by itself, meaning another build is in progress
return CauseOfBlockage.fromMessage(Messages._Queue_InProgress());
return CauseOfBlockage.fromMessage(Messages._Queue_BlockedBy(r.getDisplayName()));
}
}
return null;
}
/**
......@@ -1472,7 +1507,8 @@ public class Queue extends ResourceController implements Saveable {
for (BlockedItem p : blockedItems) {
String taskDisplayName = LOGGER.isLoggable(Level.FINEST) ? p.task.getFullDisplayName() : null;
LOGGER.log(Level.FINEST, "Current blocked item: {0}", taskDisplayName);
if (!isBuildBlocked(p) && allowNewBuildableTask(p.task)) {
CauseOfBlockage causeOfBlockage = getCauseOfBlockageForItem(p);
if (causeOfBlockage == null) {
LOGGER.log(Level.FINEST,
"BlockedItem {0}: blocked -> buildable as the build is not blocked and new tasks are allowed",
taskDisplayName);
......@@ -1487,6 +1523,8 @@ public class Queue extends ResourceController implements Saveable {
// determine if they are blocked by the lucky winner
updateSnapshot();
}
} else {
p.setCauseOfBlockage(causeOfBlockage);
}
}
}
......@@ -1501,8 +1539,8 @@ public class Queue extends ResourceController implements Saveable {
}
top.leave(this);
Task p = top.task;
if (!isBuildBlocked(top) && allowNewBuildableTask(p)) {
CauseOfBlockage causeOfBlockage = getCauseOfBlockageForItem(top);
if (causeOfBlockage == null) {
// ready to be executed immediately
Runnable r = makeBuildable(new BuildableItem(top));
String topTaskDisplayName = LOGGER.isLoggable(Level.FINEST) ? top.task.getFullDisplayName() : null;
......@@ -1511,12 +1549,12 @@ public class Queue extends ResourceController implements Saveable {
r.run();
} else {
LOGGER.log(Level.FINEST, "Item {0} was unable to be made a buildable and is now a blocked item.", topTaskDisplayName);
new BlockedItem(top).enter(this);
new BlockedItem(top, CauseOfBlockage.fromMessage(Messages._Queue_HudsonIsAboutToShutDown())).enter(this);
}
} else {
// this can't be built now because another build is in progress
// set this project aside.
new BlockedItem(top).enter(this);
new BlockedItem(top, causeOfBlockage).enter(this);
}
}
......@@ -1530,9 +1568,10 @@ public class Queue extends ResourceController implements Saveable {
for (BuildableItem p : new ArrayList<BuildableItem>(
buildables)) {// copy as we'll mutate the list in the loop
// one last check to make sure this build is not blocked.
if (isBuildBlocked(p)) {
CauseOfBlockage causeOfBlockage = getCauseOfBlockageForItem(p);
if (causeOfBlockage != null) {
p.leave(this);
new BlockedItem(p).enter(this);
new BlockedItem(p, causeOfBlockage).enter(this);
LOGGER.log(Level.FINE, "Catching that {0} is blocked in the last minute", p);
// JENKINS-28926 we have moved an unblocked task into the blocked state, update snapshot
// so that other buildables which might have been blocked by this can see the state change
......@@ -1596,7 +1635,7 @@ public class Queue extends ResourceController implements Saveable {
// The creation of a snapshot itself should be relatively cheap given the expected rate of
// job execution. You probably would need 100's of jobs starting execution every iteration
// of maintain() before this could even start to become an issue and likely the calculation
// of isBuildBlocked(p) will become a bottleneck before updateSnapshot() will. Additionally
// of getCauseOfBlockageForItem(p) will become a bottleneck before updateSnapshot() will. Additionally
// since the snapshot itself only ever has at most one reference originating outside of the stack
// it should remain in the eden space and thus be cheap to GC.
// See https://jenkins-ci.org/issue/27708?focusedCommentId=225819&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-225819
......@@ -1777,7 +1816,7 @@ public class Queue extends ResourceController implements Saveable {
* for temporary reasons.
*
* <p>
* Short-hand for {@code getCauseOfBlockage()!=null}.
* Short-hand for {@code getCauseOfBlockageForItem()!=null}.
*/
boolean isBuildBlocked();
......@@ -1802,6 +1841,9 @@ public class Queue extends ResourceController implements Saveable {
*/
@CheckForNull
default CauseOfBlockage getCauseOfBlockage() {
if (isBuildBlocked()) {
return CauseOfBlockage.fromMessage(Messages._Queue_Unknown());
}
return null;
}
......@@ -2454,29 +2496,37 @@ public class Queue extends ResourceController implements Saveable {
* {@link Item} in the {@link Queue#blockedProjects} stage.
*/
public final class BlockedItem extends NotWaitingItem {
private transient CauseOfBlockage causeOfBlockage = null;
public BlockedItem(WaitingItem wi) {
super(wi);
this(wi, null);
}
public BlockedItem(NotWaitingItem ni) {
this(ni, null);
}
BlockedItem(WaitingItem wi, CauseOfBlockage causeOfBlockage) {
super(wi);
this.causeOfBlockage = causeOfBlockage;
}
BlockedItem(NotWaitingItem ni, CauseOfBlockage causeOfBlockage) {
super(ni);
this.causeOfBlockage = causeOfBlockage;
}
public CauseOfBlockage getCauseOfBlockage() {
ResourceActivity r = getBlockingActivity(task);
if (r != null) {
if (r == task) // blocked by itself, meaning another build is in progress
return CauseOfBlockage.fromMessage(Messages._Queue_InProgress());
return CauseOfBlockage.fromMessage(Messages._Queue_BlockedBy(r.getDisplayName()));
}
void setCauseOfBlockage(CauseOfBlockage causeOfBlockage) {
this.causeOfBlockage = causeOfBlockage;
}
for (QueueTaskDispatcher d : QueueTaskDispatcher.all()) {
CauseOfBlockage cause = d.canRun(this);
if (cause != null)
return cause;
public CauseOfBlockage getCauseOfBlockage() {
if (causeOfBlockage != null) {
return causeOfBlockage;
}
return task.getCauseOfBlockage();
// fallback for backward compatibility
return getCauseOfBlockageForItem(this);
}
/*package*/ void enter(Queue q) {
......
......@@ -114,6 +114,7 @@ import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jenkins.scm.RunWithSCM.*;
......@@ -455,25 +456,49 @@ public abstract class View extends AbstractModelObject implements AccessControll
return false;
}
private final static int FILTER_LOOP_MAX_COUNT = 10;
private List<Queue.Item> filterQueue(List<Queue.Item> base) {
if (!isFilterQueue()) {
return base;
}
Collection<TopLevelItem> items = getItems();
List<Queue.Item> result = new ArrayList<Queue.Item>();
for (Queue.Item qi : base) {
if (items.contains(qi.task)) {
result.add(qi);
} else
if (qi.task instanceof AbstractProject<?, ?>) {
AbstractProject<?,?> project = (AbstractProject<?, ?>) qi.task;
if (items.contains(project.getRootProject())) {
result.add(qi);
}
return base.stream().filter(qi -> filterQueueItemTest(qi, items))
.collect(Collectors.toList());
}
private boolean filterQueueItemTest(Queue.Item item, Collection<TopLevelItem> viewItems) {
// Check if the task of parent tasks are in the list of viewItems.
// Pipeline jobs and other jobs which allow parts require us to
// check owner tasks as well.
Queue.Task currentTask = item.task;
for (int count = 1;; count++) {
if (viewItems.contains(currentTask)) {
return true;
}
Queue.Task next = currentTask.getOwnerTask();
if (next == currentTask) {
break;
} else {
currentTask = next;
}
if (count == FILTER_LOOP_MAX_COUNT) {
LOGGER.warning(String.format(
"Failed to find root task for queue item '%s' for " +
"view '%s' in under %d iterations, aborting!",
item.getDisplayName(), getDisplayName(),
FILTER_LOOP_MAX_COUNT));
break;
}
}
return result;
// Check root project for sub-job projects (e.g. matrix jobs).
if (item.task instanceof AbstractProject<?, ?>) {
AbstractProject<?,?> project = (AbstractProject<?, ?>) item.task;
if (viewItems.contains(project.getRootProject())) {
return true;
}
}
return false;
}
public List<Queue.Item> getQueueItems() {
......
......@@ -130,8 +130,8 @@ public class XStream2 extends XStream {
* Specifies that a given field of a given class should not be treated with laxity by {@link RobustCollectionConverter}.
* @param clazz a class which we expect to hold a non-{@code transient} field
* @param field a field name in that class
* @since TODO
*/
@Restricted(NoExternalUse.class) // TODO could be opened up later
public void addCriticalField(Class<?> clazz, String field) {
reflectionConverter.addCriticalField(clazz, field);
}
......
/*
* The MIT License
*
* Copyright (c) 2017, 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.security.csrf;
import hudson.Extension;
import hudson.model.AdministrativeMonitor;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Monitor that the CSRF protection is enabled on the application.
*
* @since TODO
*/
@Extension
@Symbol("csrf")
@Restricted(NoExternalUse.class)
public class CSRFAdministrativeMonitor extends AdministrativeMonitor {
@Override
public String getDisplayName() {
return Messages.CSRFAdministrativeMonitor_displayName();
}
@Override
public boolean isActivated() {
return Jenkins.getInstance().getCrumbIssuer() == null;
}
}
......@@ -32,10 +32,10 @@ THE SOFTWARE.
<j:new var="h" className="hudson.Functions" />
<Projects>
<j:forEach var="p" items="${it.items}">
<j:forEach var="p" items="${h.getCCItems(it)}">
<j:set var="lb" value="${p.lastCompletedBuild}"/>
<j:if test="${lb!=null}">
<Project name="${p.displayName}"
<Project name="${p.fullDisplayName}"
activity="${p.isBuilding() ? 'Building' : 'Sleeping'}"
lastBuildStatus="${h.toCCStatus(p)}"
lastBuildLabel="${lb.number}"
......
<!--
The MIT License
Copyright (c) 2017, 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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core">
<div class="warning">
${%warningMessage(rootURL)}
</div>
</j:jelly>
warningMessage=You have not configured the CSRF issuer. This could be a security issue. \
For more information, please refer to <a href="https://jenkins.io/redirect/csrf-protection" target="_blank">this page</a>. \
<br /> \
You can change the current configuration using the Security section <a href="{0}/configureSecurity">CSRF Protection</a>.
CSRFAdministrativeMonitor.displayName=CSRF Protection Monitor
......@@ -37,6 +37,10 @@ public class MockItem extends Queue.Item {
super(null, Collections.<Action>emptyList(), id, null);
}
public MockItem(Queue.Task task) {
super(task, Collections.emptyList(), -1, null);
}
public MockItem(Queue.Task task, List<Action> actions, long id) {
super(task, actions, id, null);
}
......
......@@ -22,6 +22,7 @@ import org.junit.Test;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.mockito.Mockito;
import org.powermock.reflect.Whitebox;
public class ViewTest {
......@@ -112,6 +113,62 @@ public class ViewTest {
return rootJob;
}
@Test
public void buildQueueFiltering() throws Exception {
// Mimic a freestyle job
FreeStyleProject singleItemJob = Mockito.mock(FreeStyleProject.class);
Mockito.when(singleItemJob.getOwnerTask()).thenReturn(singleItemJob);
Queue.Item singleItemQueueItem = new MockItem(singleItemJob);
// Mimic pattern of a Matrix job, i.e. with root item in view and sub
// items in queue.
FreeStyleProject multiItemJob = Mockito.mock(FreeStyleProject.class);
Project multiItemSubJob = Mockito.mock(Project.class);
Mockito.when(multiItemSubJob.getRootProject()).thenReturn(multiItemJob);
Mockito.when(multiItemSubJob.getOwnerTask()).thenReturn(multiItemSubJob);
Queue.Item multiItemQueueItem = new MockItem(multiItemSubJob);
// Mimic pattern of a Pipeline job, i.e. with item in view and
// sub-steps in queue.
BuildableTopLevelItem multiStepJob
= Mockito.mock(BuildableTopLevelItem.class);
Mockito.when(multiStepJob.getOwnerTask()).thenReturn(multiStepJob);
BuildableItem multiStepSubStep = Mockito.mock(BuildableItem.class);
Mockito.when(multiStepSubStep.getOwnerTask()).thenReturn(multiStepJob);
Queue.Item multiStepQueueItem = new MockItem(multiStepSubStep);
// Construct the view
View view = Mockito.mock(View.class);
List<Queue.Item> queue = Arrays.asList(singleItemQueueItem,
multiItemQueueItem, multiStepQueueItem);
Mockito.when(view.isFilterQueue()).thenReturn(true);
// Positive test, ensure that queue items are included
List<TopLevelItem> viewJobs = Arrays.asList(singleItemJob, multiItemJob, multiStepJob);
Mockito.when(view.getItems()).thenReturn(viewJobs);
assertEquals(
Arrays.asList(singleItemQueueItem,
multiItemQueueItem, multiStepQueueItem),
Whitebox.invokeMethod(view, "filterQueue", queue)
);
// Negative test, ensure that queue items are excluded
Mockito.when(view.getItems()).thenReturn(Collections.emptyList());
List<Queue.Item> expected = Arrays.asList(singleItemQueueItem,
multiItemQueueItem, multiStepQueueItem);
assertEquals(
Collections.emptyList(),
Whitebox.<List<Queue.Item>>invokeMethod(view, "filterQueue", queue)
);
}
/**
* This interface fulfills both TopLevelItem and BuildableItem interface,
* this allows it for being present in a view as well as the build queue!
*/
private interface BuildableTopLevelItem extends TopLevelItem, BuildableItem {
}
public static class CompositeView extends View implements ViewGroup {
private View[] views;
......
......@@ -33,7 +33,7 @@ THE SOFTWARE.
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>2.85-SNAPSHOT</version>
<version>2.86-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Jenkins main module</name>
......@@ -171,7 +171,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>remoting</artifactId>
<version>3.12</version>
<version>3.13</version>
</dependency>
<dependency>
......
......@@ -28,7 +28,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>2.85-SNAPSHOT</version>
<version>2.86-SNAPSHOT</version>
</parent>
<artifactId>test</artifactId>
......
......@@ -69,6 +69,7 @@ import hudson.triggers.SCMTrigger.SCMTriggerCause;
import hudson.triggers.TimerTrigger.TimerTriggerCause;
import hudson.util.OneShotEvent;
import hudson.util.XStream2;
import jenkins.model.BlockedBecauseOfBuildInProgress;
import jenkins.model.Jenkins;
import jenkins.security.QueueItemAuthenticatorConfiguration;
import jenkins.triggers.ReverseBuildTrigger;
......@@ -529,16 +530,24 @@ public class QueueTest {
}
static class TestTask implements Queue.Task {
private final AtomicInteger cnt;
private final boolean isBlocked;
TestTask(AtomicInteger cnt) {
this(cnt, false);
}
TestTask(AtomicInteger cnt, boolean isBlocked) {
this.cnt = cnt;
this.isBlocked = isBlocked;
}
@Override public boolean equals(Object o) {
return o instanceof TestTask && cnt == ((TestTask) o).cnt;
}
@Override public int hashCode() {
return cnt.hashCode();
}
@Override public boolean isBuildBlocked() {return false;}
@Override public boolean isBuildBlocked() {return isBlocked;}
@Override public String getWhyBlocked() {return null;}
@Override public String getName() {return "test";}
@Override public String getFullDisplayName() {return "Test";}
......@@ -771,7 +780,7 @@ public class QueueTest {
final FreeStyleProject projectB = r.createFreeStyleProject(prefix+"B");
projectB.getBuildersList().add(new SleepBuilder(10000));
projectB.setBlockBuildWhenUpstreamBuilding(true);
final FreeStyleProject projectC = r.createFreeStyleProject(prefix+"C");
projectC.getBuildersList().add(new SleepBuilder(10000));
projectC.setBlockBuildWhenUpstreamBuilding(true);
......@@ -978,4 +987,37 @@ public class QueueTest {
};
}
}
@Test
public void testDefaultImplementationOfGetCauseOfBlockageForBlocked() throws Exception {
Queue queue = r.getInstance().getQueue();
queue.schedule2(new TestTask(new AtomicInteger(0), true), 0);
queue.maintain();
assertEquals(1, r.jenkins.getQueue().getBlockedItems().size());
CauseOfBlockage actual = r.jenkins.getQueue().getBlockedItems().get(0).getCauseOfBlockage();
CauseOfBlockage expected = CauseOfBlockage.fromMessage(Messages._Queue_Unknown());
assertEquals(expected.getShortDescription(), actual.getShortDescription());
}
@Test
public void testGetCauseOfBlockageForNonConcurrentFreestyle() throws Exception {
Queue queue = r.getInstance().getQueue();
FreeStyleProject t1 = r.createFreeStyleProject("project");
t1.getBuildersList().add(new SleepBuilder(TimeUnit.SECONDS.toMillis(30)));
t1.setConcurrentBuild(false);
t1.scheduleBuild2(0).waitForStart();
t1.scheduleBuild2(0);
queue.maintain();
assertEquals(1, r.jenkins.getQueue().getBlockedItems().size());
CauseOfBlockage actual = r.jenkins.getQueue().getBlockedItems().get(0).getCauseOfBlockage();
CauseOfBlockage expected = new BlockedBecauseOfBuildInProgress(t1.getFirstBuild());
assertEquals(expected.getShortDescription(), actual.getShortDescription());
}
}
......@@ -454,4 +454,43 @@ public class SearchTest {
index.suggest(term, result);
return result;
}
@Issue("JENKINS-35459")
@Test
public void testProjectNameInAListView() throws Exception {
MockFolder myMockFolder = j.createFolder("folder");
FreeStyleProject freeStyleProject = myMockFolder.createProject(FreeStyleProject.class, "myJob");
ListView listView = new ListView("ListView", j.jenkins);
listView.setRecurse(true);
listView.add(myMockFolder);
listView.add(freeStyleProject);
j.jenkins.addView(listView);
j.jenkins.setPrimaryView(listView);
assertEquals(2, j.jenkins.getPrimaryView().getAllItems().size());
WebClient wc = j.createWebClient();
Page result = wc.goTo("search/suggest?query=" + freeStyleProject.getName(), "application/json");
assertNotNull(result);
j.assertGoodStatus(result);
String content = result.getWebResponse().getContentAsString();
JSONObject jsonContent = (JSONObject)JSONSerializer.toJSON(content);
assertNotNull(jsonContent);
JSONArray jsonArray = jsonContent.getJSONArray("suggestions");
assertNotNull(jsonArray);
assertEquals(2, jsonArray.size());
Page searchResult = wc.goTo("search?q=" + myMockFolder.getName() + "%2F" + freeStyleProject.getName());
assertNotNull(searchResult);
j.assertGoodStatus(searchResult);
URL resultUrl = searchResult.getUrl();
assertTrue(resultUrl.toString().equals(j.getInstance().getRootUrl() + freeStyleProject.getUrl()));
}
}
......@@ -40,6 +40,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
......@@ -55,7 +57,8 @@ public class AgentProtocolTest {
@Rule
public JenkinsRule j = new JenkinsRule();
//TODO: Test is unstable on CI due to the race condition, needs to be reworked
/**
* Checks that Jenkins does not disable agent protocols by default after the upgrade.
*
......@@ -63,6 +66,7 @@ public class AgentProtocolTest {
* @see SetupWizardTest#shouldDisableUnencryptedProtocolsByDefault()
*/
@Test
@Ignore
@LocalData
@Issue("JENKINS-45841")
public void testShouldNotDisableProtocolsForMigratedInstances() throws Exception {
......
/*
* The MIT License
*
* Copyright (c) 2017, 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.security.csrf;
import hudson.model.AdministrativeMonitor;
import hudson.security.csrf.DefaultCrumbIssuer;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class CSRFAdministrativeMonitorTest {
@Rule
public JenkinsRule j = new JenkinsRule();
@Test
@Issue("JENKINS-47372")
public void testWithoutIssuer() {
j.jenkins.setCrumbIssuer(null);
CSRFAdministrativeMonitor monitor = j.jenkins.getExtensionList(AdministrativeMonitor.class).get(CSRFAdministrativeMonitor.class);
assertTrue("Monitor must not be activated", monitor.isActivated());
}
@Test
@Issue("JENKINS-47372")
public void testWithIssuer() {
j.jenkins.setCrumbIssuer(new DefaultCrumbIssuer(false));
CSRFAdministrativeMonitor monitor = j.jenkins.getExtensionList(AdministrativeMonitor.class).get(CSRFAdministrativeMonitor.class);
assertFalse("Monitor must be activated", monitor.isActivated());
}
}
......@@ -28,7 +28,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>2.85-SNAPSHOT</version>
<version>2.86-SNAPSHOT</version>
</parent>
<artifactId>jenkins-war</artifactId>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册