/*
* 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 java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sonyericsson.hudson.plugins.metadata.model.MetadataBuildAction;
import com.sonyericsson.hudson.plugins.metadata.model.values.TreeNodeMetadataValue;
import com.sonyericsson.hudson.plugins.metadata.model.values.TreeStructureUtil;
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.selection.AbstractResourceSelection;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.utils.AdminNotifier;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.utils.resourcemanagers.ExternalResourceManager;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Node;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.export.ExportedBean;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Hudson;
import hudson.model.JobProperty;
import hudson.model.JobPropertyDescriptor;
import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.ExternalResource;
import static com.sonyericsson.jenkins.plugins.externalresource.dispatcher.Constants.BUILD_LOCKED_RESOURCE_NAME;
import static com.sonyericsson.jenkins.plugins.externalresource.dispatcher.Constants.getBuildLockedResourceParentPath;
/**
* Holder for the user specified criteria about what resource the build wants to use.
*
* @author Robert Sandell <robert.sandell@sonyericsson.com>
*/
@ExportedBean
public class SelectionCriteria extends JobProperty<AbstractProject<?, ?>> {
private static final Logger logger = Logger.getLogger(SelectionCriteria.class.getName());
private boolean selectionEnabled;
private List<AbstractResourceSelection> resourceSelectionList;
/**
* Standard DataBound Constructor.
*
* @param selectionEnabled if true, selection is checked
* @param resourceSelectionList the selection list
*/
@DataBoundConstructor
public SelectionCriteria(boolean selectionEnabled, List<AbstractResourceSelection> resourceSelectionList) {
this.selectionEnabled = selectionEnabled;
this.resourceSelectionList = resourceSelectionList;
}
/**
* Standard Constructor.
*
* @param resourceSelectionList the selection list
*/
public SelectionCriteria(List<AbstractResourceSelection> resourceSelectionList) {
this.resourceSelectionList = resourceSelectionList;
}
/**
* The list of resource selection, if null, then create a new list.
*
* @return all the resource selections
*/
public synchronized List<AbstractResourceSelection> getResourceSelectionList() {
if (resourceSelectionList == null) {
resourceSelectionList = new LinkedList<AbstractResourceSelection>();
}
return resourceSelectionList;
}
/**
* SelectionEnabled value.
*
* @return true if selection is enabled
*/
public boolean getSelectionEnabled() {
return selectionEnabled;
}
/**
* Get matching resource from available resources.
*
* @param availableResourceList
* available resources list.
* @return the matching resource list if exists.
*/
public List<ExternalResource> getMatchingResources(List<ExternalResource> availableResourceList) {
List<ExternalResource> matchingResourceList = new LinkedList<ExternalResource>();
boolean foundFlag = true;
for (ExternalResource er : availableResourceList) {
foundFlag = true;
for (AbstractResourceSelection resourceSelection : resourceSelectionList) {
if (!resourceSelection.equalToExternalResourceValue(er)) {
foundFlag = false;
break;
}
}
if (foundFlag) {
matchingResourceList.add(er);
}
}
return matchingResourceList;
}
@Override
public boolean prebuild(AbstractBuild<?, ?> build,
BuildListener listener) {
if (!getSelectionEnabled()) {
logger.log(Level.FINE, "Selection not enabled, continuing");
return true;
}
String buildName = build.getFullDisplayName();
Node node = build.getBuiltOn();
ReservedExternalResourceAction action = build.getAction(ReservedExternalResourceAction.class);
if (action == null) {
AdminNotifier.getInstance().notify(AdminNotifier.MessageType.ERROR, AdminNotifier.OperationType.RESERVE,
node, null, "No phone chosen even though we have selection criteria, aborting build: "
+ buildName);
logger.log(Level.SEVERE,
"No phone chosen even though we have selection criteria, aborting build: [{0}].", buildName);
listener.getLogger().println(
"No phone chosen even though we have selection criteria, aborting build.");
return false;
}
ExternalResource reserved = action.pop();
StashInfo reservedInfo = reserved.getReserved();
ExternalResourceManager resourceManager = PluginImpl.getInstance().getManager();
//If the phone is not reserved anymore, try to reserve it again.
//If it cannot be reserved, fail the build.
if (reservedInfo == null) {
StashResult result = resourceManager.reserve(node, reserved, PluginImpl.getInstance().getReserveTime(),
build.getDisplayName());
if (result == null || !result.isOk()) {
AdminNotifier.getInstance().notify(AdminNotifier.MessageType.ERROR,
AdminNotifier.OperationType.RESERVE, node, reserved,
"The external resource has been taken by someone else, aborting build: " + buildName);
logger.log(Level.SEVERE, "External resource: [{0}] has been taken by someone else, aborting build",
reserved.getId());
listener.getLogger().println("External resource: " + reserved.getId()
+ " has been taken by someone else, aborting build");
return false;
} else {
reservedInfo = new StashInfo(result, build.getUrl());
}
}
//we have a reserved phone, now lock it.
StashResult lockResult = resourceManager.lock(node, reserved, reservedInfo.getKey(),
build.getUrl());
if (lockResult == null || !lockResult.isOk()) {
AdminNotifier.getInstance().notify(AdminNotifier.MessageType.ERROR, AdminNotifier.OperationType.LOCK,
node, reserved, "Could not lock resource, aborting the build: " + buildName);
logger.log(Level.SEVERE, "Could not lock resource: [{0}], aborting the build: [{1}].",
new String[]{reserved.getId(), buildName});
listener.getLogger().println("Could not lock resource: " + reserved.getId() + ", aborting the build.");
return false;
}
//update the node and build information.
ExternalResource locked;
try {
locked = reserved.clone();
} catch (CloneNotSupportedException e) {
//should not happen since ExternalResource and its ancestors are cloneable.
AdminNotifier.getInstance().notify(AdminNotifier.MessageType.ERROR, AdminNotifier.OperationType.LOCK,
node, reserved, "Could not clone the External resource, aborting the build: " + buildName);
logger.log(Level.SEVERE,
"Could not clone the External resource: [{0}], aborting the build: [{1}].",
new String[]{reserved.getId(), buildName});
listener.getLogger().println(
"Could not clone the External resource: " + reserved.getId() + ", aborting the build.");
return false;
}
MetadataBuildAction metadataBuildAction = build.getAction(MetadataBuildAction.class);
if (metadataBuildAction == null) {
metadataBuildAction = new MetadataBuildAction(build);
build.addAction(metadataBuildAction);
}
locked.setName(BUILD_LOCKED_RESOURCE_NAME);
TreeNodeMetadataValue lockedTree = TreeStructureUtil.createPath(locked, getBuildLockedResourceParentPath());
metadataBuildAction.addChild(lockedTree);
locked.setExposeToEnvironment(true);
//The resource has been locked and we can continue with the build.
return true;
}
/**
* Descriptor for {@link SelectionCriteria}.
*/
@Extension
public static class SelectionCriteriaDescriptor extends JobPropertyDescriptor {
@Override
public String getDisplayName() {
return Messages.SelectionCriteria_DisplayName();
}
/**
* All registered resource selection descriptors that applies to jobs. To be used by a hetero-list.
*
* Get descriptor list.
* @return the descriptor list.
*/
public List<AbstractResourceSelection.AbstractResourceSelectionDescriptor> getResourceSelectionDescriptors() {
return Hudson.getInstance()
.getExtensionList(AbstractResourceSelection.AbstractResourceSelectionDescriptor.class);
}
}
}