/*
* The MIT License
*
* Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved.
* Copyright 2012 Sony Mobile Communications AB. All rights reserved.
*
* 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 com.sonyericsson.jenkins.plugins.externalresource.dispatcher;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.ExternalResource;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.ReservedExternalResourceAction;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.StashInfo;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.StashResult;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.veto.BecauseNoAvailableResources;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.veto.BecauseNoMatchingResource;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.veto.BecauseNothingReserved;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.veto.BecauseAlreadyReserved;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.utils.AdminNotifier;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.utils.AvailabilityFilter;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.utils.resourcemanagers.ExternalResourceManager;
import hudson.Extension;
import hudson.matrix.MatrixConfiguration;
import hudson.model.AbstractProject;
import hudson.model.Node;
import hudson.model.Queue;
import hudson.model.queue.CauseOfBlockage;
import hudson.model.queue.QueueTaskDispatcher;
import jenkins.model.Jenkins;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The main veto engine. If a node has an available and matching resource, the resource will be reserved and OKd to be
* scheduled on that node. Otherwise the build will be blocked from that Node.
*
* @author Robert Sandell <robert.sandell@sonyericsson.com>
*/
@Extension(ordinal = Constants.QTD_ORDINAL)
public class ExternalResourceQueueTaskDispatcher extends QueueTaskDispatcher {
private static final Logger logger = Logger.getLogger(ExternalResourceQueueTaskDispatcher.class.getName());
@Override
public CauseOfBlockage canTake(Node node, Queue.BuildableItem item) {
logger.entering("ExternalResourceQueueTaskDispatcher", "canTake", new Object[]{node, item});
// check whether there is already something reserved for use. skip the following step if so.
// the cantake() method will be called several times, depending on how many available executors left.
ReservedExternalResourceAction storage = getReservedExternalResourceAction(item);
if (!storage.isEmpty()) { // if already something there.
// return a blockage cause to avoid the executor joining to candidates list once we already have one.
logger.exiting("ExternalResourceQueueTaskDispatcher", "canTake", "BecauseAlreadyReserved");
return new BecauseAlreadyReserved();
}
SelectionCriteria selectionCriteria = getSelectionCriteria(item.task);
if (selectionCriteria == null
|| !selectionCriteria.getSelectionEnabled()
|| selectionCriteria.getResourceSelectionList() == null
|| selectionCriteria.getResourceSelectionList().isEmpty()) {
//Either it is not a buildable item that we are interested in, or it is a project that
// doesn't have a configured criteria. So we say ok.
logger.exiting("ExternalResourceQueueTaskDispatcher", "canTake", "OK - not buildable or no selection");
return null;
}
AvailabilityFilter availabilityFilter = AvailabilityFilter.getInstance();
//Find all resources
List<ExternalResource> resources = availabilityFilter.getExternalResourcesList(node);
if (resources == null || resources.isEmpty()) {
//No resources configured, block the build on this node.
logger.exiting("ExternalResourceQueueTaskDispatcher", "canTake", "BecauseNoAvailableResources-1");
return new BecauseNoAvailableResources(node);
}
//Filter out what is available
resources = availabilityFilter.filterEnabledAndAvailable(resources);
if (resources == null || resources.isEmpty()) {
//No available resources, block the build on this node.
logger.exiting("ExternalResourceQueueTaskDispatcher", "canTake", "BecauseNoAvailableResources-2");
return new BecauseNoAvailableResources(node);
}
resources = selectionCriteria.getMatchingResources(resources);
if (resources == null || resources.isEmpty()) {
//No matching resources, block the build on this node.
logger.exiting("ExternalResourceQueueTaskDispatcher", "canTake", "BecauseNoMatchingResource");
return new BecauseNoMatchingResource(node);
}
//Reserve something
ExternalResource reservedResource = null;
ExternalResourceManager manager = PluginImpl.getInstance().getManager();
for (ExternalResource resource : resources) {
StashResult result = manager.reserve(node, resource, PluginImpl.getInstance().getReserveTime(),
item.task.getUrl());
logger.log(Level.FINEST, "Reserve result for [{0}]: Status {1} code {2} message {3}",
new Object[]{resource.getFullName(), result.getStatus().name(),
result.getErrorCode(), result.getMessage(), });
if (result != null && result.isOk()) {
reservedResource = resource;
logger.finest("reservation ok");
break;
} else {
logger.finest("Not reserved");
}
}
if (reservedResource == null) {
//None of the matching resources could be reserved, block the build
AdminNotifier.getInstance().notify(AdminNotifier.MessageType.WARNING, AdminNotifier.OperationType.RESERVE,
node, reservedResource, "Found one or more matching external resources but could not reserve any "
+ "of them.");
logger.exiting("ExternalResourceQueueTaskDispatcher", "canTake", "BecauseNothingReserved");
return new BecauseNothingReserved(node);
}
//Cannot create a metadata action since it requires a build. Temporarily storing it in a separate action.
storage.push(reservedResource);
//Everything is fine, now continue.
logger.exiting("ExternalResourceQueueTaskDispatcher", "canTake", "OK");
return null;
}
/**
* Finds the action or creates and adds it to the item if it doesn't exsist.
*
* @param item the build to be.
* @return the storage.
*/
private ReservedExternalResourceAction getReservedExternalResourceAction(Queue.BuildableItem item) {
List<ReservedExternalResourceAction> actions = item.getActions(ReservedExternalResourceAction.class);
// maintain the actions for run out leases
Iterator<ReservedExternalResourceAction> iterator = actions.iterator();
List<ReservedExternalResourceAction> toRemove = new ArrayList<ReservedExternalResourceAction>();
while (iterator.hasNext()) {
ReservedExternalResourceAction act = iterator.next();
act.maintain();
if (act.isEmpty()) {
toRemove.add(act);
}
}
if (!toRemove.isEmpty()) {
item.getActions().removeAll(toRemove);
actions.removeAll(toRemove);
}
if (actions != null && actions.size() > 0) {
return actions.get(0);
} else {
ReservedExternalResourceAction storage = new ReservedExternalResourceAction();
item.addAction(storage);
return storage;
}
}
/**
* Gets the internal URL to the build to be, or just the project if we can't find one.
*
* @param task the task
* @return the url to the "task"
*/
private String getUrl(Queue.Task task) {
AbstractProject<?, ?> p = getProject(task);
if (p != null) {
//TODO find the build and return that URL if possible.
return p.getAbsoluteUrl();
}
return "";
}
/**
* Gets the project that this task represents.
*
* @param task the task
* @return the project.
*/
private AbstractProject getProject(Queue.Task task) {
if (task instanceof AbstractProject) {
AbstractProject<?, ?> p = (AbstractProject<?, ?>)task;
if (task instanceof MatrixConfiguration) {
p = (AbstractProject<?, ?>)((MatrixConfiguration)task).getParent();
}
return p;
}
return null;
}
/**
* Gets the selection criteria from the task if there is any.
*
* @param task the task.
* @return the selection criteria or null if there is none configured.
*/
private SelectionCriteria getSelectionCriteria(Queue.Task task) {
AbstractProject<?, ?> p = getProject(task);
if (p != null) {
return p.getProperty(SelectionCriteria.class);
}
return null;
}
}