/* * 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.cli; import com.sonyericsson.hudson.plugins.metadata.cli.CliResponse; import com.sonyericsson.hudson.plugins.metadata.cli.CliUtils; import com.sonyericsson.hudson.plugins.metadata.cli.CliResponse.Type; import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.Constants; import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.ExternalResource; import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.StashInfo; import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.utils.AvailabilityFilter; import hudson.Extension; import hudson.model.Hudson; import hudson.model.Node; import hudson.model.RootAction; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerResponse; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import static com.sonyericsson.hudson.plugins.metadata.cli.CliResponse.sendIgnored; import static com.sonyericsson.hudson.plugins.metadata.cli.CliResponse.sendOk; import static com.sonyericsson.hudson.plugins.metadata.cli.CliResponse.sendResponse; /** * Http interface for the CLI commands. * <p/> * As some systems prefer to have a bit more intimate call API towards other systems than what {@link * hudson.cli.CLICommand}s provide. This action exposes {@link ExternalResource#doEnable(boolean)} * to a standard HTTP post or GET. * * @author Robert Sandell <robert.sandell@sonyericsson.com> */ @Extension public class ExternalResourceHttpCommands implements RootAction { private static final Logger logger = Logger.getLogger(ExternalResourceHttpCommands.class.getName()); @Override public String getIconFileName() { return null; } @Override public String getDisplayName() { return null; } @Override public String getUrlName() { return Constants.EXTERNAL_RESOURCE_HTTP_COMMANDS_URL; } /** * Enables an {@link ExternalResource} with the id on a given node. The JSON output is similar to the metadata * commands. * * @param node the node where the resource is located. * @param id the id of the resource. * @param response the response handle to write to. * @throws IOException if so. * @see CliResponse */ @SuppressWarnings("unused") public void doEnable( @QueryParameter(value = "node", required = true) final String node, @QueryParameter(value = "id", required = true) final String id, StaplerResponse response) throws IOException { doEnableDisable(node, id, true, response); } /** * Disables an {@link ExternalResource} with the id on a given node. The JSON output is similar to the metadata * commands. * * @param node the node where the resource is located. * @param id the id of the resource. * @param response the response handle to write to. * @throws IOException if so. * @see CliResponse */ @SuppressWarnings("unused") public void doDisable( @QueryParameter(value = "node", required = true) final String node, @QueryParameter(value = "id", required = true) final String id, StaplerResponse response) throws IOException { doEnableDisable(node, id, false, response); } /** * Does the enable/disable. * * @param node the node where the resource is located. * @param id the id of the resource. * @param enable true to enable, false to disable. * @param response the response handle to write to. * @throws IOException if so. * @see #doEnable(String, String, org.kohsuke.stapler.StaplerResponse) * @see #doDisable(String, String, org.kohsuke.stapler.StaplerResponse) * @see ExternalResource#doEnable(boolean) */ private void doEnableDisable(final String node, final String id, final boolean enable, StaplerResponse response) throws IOException { Something something = new Something() { @Override public void doIt(ExternalResource resource, StaplerResponse response) throws IOException { try { resource.doEnable(enable); sendOk(response); } catch (IOException e) { logger.log(Level.WARNING, "Probably failed to save the node config to disk! ", e); sendResponse(Type.warning, 0, "Warning", "Failed to save the changes to disk, but the resource state has changed.", response); } } }; doSomething(node, id, something, response); } /** * Make an {@link ExternalResource} reservation expired with the id on a given node. The JSON output is similar to * the metadata commands. * * @param node the node where the resource is located. * @param id the id of the resource. * @param response the response handle to write to. * @throws IOException if so. * @see CliResponse */ @SuppressWarnings("unused") public void doExpireReservation( @QueryParameter(value = "node", required = true) final String node, @QueryParameter(value = "id", required = true) final String id, StaplerResponse response) throws IOException { Something something = new Something() { @Override public void doIt(ExternalResource resource, StaplerResponse response) throws IOException { try { resource.doExpireReservation(); sendOk(response); } catch (IOException e) { logger.log(Level.WARNING, "Probably failed to save the node config to disk! ", e); sendResponse(Type.warning, 0, "Warning", "Failed to save the changes to disk, but the resource state has changed.", response); } } }; doSomething(node, id, something, response); } /** * Signal from an external lock handler that an {@link ExternalResource} has been locked. * * @param node the node where the resource is located. * @param id the id of the resource. * @param response the response handle to write to. * @param lockedBy a String describing what has locked the resource. * @param clientInfo the information about the client that called this. * @throws IOException if so. * @see CliResponse */ @SuppressWarnings("unused") public void doLockResource( @QueryParameter(value = "node", required = true) final String node, @QueryParameter(value = "id", required = true) final String id, @QueryParameter(value = "lockedBy", required = true) final String lockedBy, @QueryParameter(value = "clientInfo", required = true) final String clientInfo, StaplerResponse response) throws IOException { Something something = new Something() { @Override public void doIt(ExternalResource resource, StaplerResponse response) throws IOException { try { if (ErCliUtils.isRequestCircular(clientInfo)) { logger.log(Level.FINE, "Request was circular for: {0}", clientInfo); sendIgnored(response); return; } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Locking resource: {0} on node: {1} with clientInfo: {2}", new Object[]{id, node, clientInfo, }); } StashInfo lockedInfo = new StashInfo(StashInfo.StashType.EXTERNAL, lockedBy, null, null); resource.doLock(lockedInfo); sendOk(response); } catch (IOException e) { logger.log(Level.WARNING, "Probably failed to save the node config to disk! ", e); sendResponse(Type.warning, 0, "Warning", "Failed to save the changes to disk, but the resource state has changed.", response); } } }; doSomething(node, id, something, response); } /** * Signal from an external lock handler that an {@link ExternalResource} has been reserved. * * @param node the node where the resource is located. * @param id the id of the resource. * @param reservedBy a String describing what has reserved the resource. * @param clientInfo the information about the client that called this. * @param response the response handle to write to. * @throws IOException if so. * @see CliResponse */ @SuppressWarnings("unused") public void doReserveResource( @QueryParameter(value = "node", required = true) final String node, @QueryParameter(value = "id", required = true) final String id, @QueryParameter(value = "reservedBy", required = true) final String reservedBy, @QueryParameter(value = "clientInfo", required = true) final String clientInfo, StaplerResponse response) throws IOException { Something something = new Something() { @Override public void doIt(ExternalResource resource, StaplerResponse response) throws IOException { try { if (ErCliUtils.isRequestCircular(clientInfo)) { logger.log(Level.FINE, "Request was circular for: {0} ", clientInfo); sendIgnored(response); return; } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Reserving resource: {0} on node: {1} with clientInfo: {2}", new Object[]{id, node, clientInfo, }); } StashInfo reservedInfo = new StashInfo(StashInfo.StashType.EXTERNAL, reservedBy, null, null); resource.doReserve(reservedInfo); sendOk(response); } catch (IOException e) { logger.log(Level.WARNING, "Probably failed to save the node config to disk! ", e); sendResponse(Type.warning, 0, "Warning", "Failed to save the changes to disk, but the resource state has changed.", response); } } }; doSomething(node, id, something, response); } /** * Signal from an external lock handler that an {@link ExternalResource} has been released. * * @param node the node where the resource is located. * @param id the id of the resource. * @param clientInfo the information about the client that called this. * @param response the response handle to write to. * @throws IOException if so. * @see CliResponse */ @SuppressWarnings("unused") public void doReleaseResource( @QueryParameter(value = "node", required = true) final String node, @QueryParameter(value = "id", required = true) final String id, @QueryParameter(value = "clientInfo", required = true) final String clientInfo, StaplerResponse response) throws IOException { Something something = new Something() { @Override public void doIt(ExternalResource resource, StaplerResponse response) throws IOException { try { if (ErCliUtils.isRequestCircular(clientInfo)) { logger.log(Level.FINE, "Request was circular for: {0}", clientInfo); sendIgnored(response); return; } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Releasing resource: {0} on node: {1} with clientInfo: {2}", new Object[]{id, node, clientInfo, }); } resource.doRelease(); sendOk(response); } catch (IOException e) { logger.log(Level.WARNING, "Probably failed to save the node config to disk! ", e); sendResponse(Type.warning, 0, "Warning", "Failed to save the changes to disk, but the resource state has changed.", response); } catch (Exception e) { logger.log(Level.WARNING, "Could not release resource! ", e); sendResponse(Type.warning, 0, "Warning", "Could not release resource.", response); } } }; doSomething(node, id, something, response); } /** * Does something with an external resource. * * @param node the node where the resource is located. * @param id the id of the resource. * @param something the actual Operation to perform. * @param response the response handle to write to. * @throws IOException if so. * @see #doEnable(String, String, org.kohsuke.stapler.StaplerResponse) * @see #doDisable(String, String, org.kohsuke.stapler.StaplerResponse) * @see ExternalResource#doEnable(boolean) * @see Something */ private void doSomething(final String node, final String id, Something something, StaplerResponse response) throws IOException { Node theNode = Hudson.getInstance().getNode(node); if (theNode != null) { ExternalResource resource = AvailabilityFilter.getInstance().getExternalResourceById(theNode, id); if (resource != null) { something.doIt(resource, response); } else { CliResponse.sendError(CliUtils.Status.ERR_NO_METADATA, "No resource with id " + id + " exists on this node.", response); } } else { CliResponse.sendError(CliUtils.Status.ERR_NO_ITEM, "No node with name " + node + " exists on this Jenkins server.", response); } } /** * Interface for performing an action on an {@link ExternalResource}. * It is fed to the method * {@link ExternalResourceHttpCommands# * doSomething(String, String, ExternalResourceHttpCommands.Something, StaplerResponse)} * so that all commands on a particular resource can have a uniform behaviour. */ private interface Something { /** * Performs the intended action on the resource. * * @param resource the resource to perform the action on. * @param response the response handle to send an eventual ok or specific error on. * @throws IOException if any response failed to be sent. */ void doIt(ExternalResource resource, StaplerResponse response) throws IOException; } }