package hudson.plugins.build_publisher; import hudson.matrix.MatrixBuild; import hudson.matrix.MatrixConfiguration; import hudson.matrix.MatrixProject; import hudson.matrix.MatrixRun; import hudson.maven.MavenBuild; import hudson.maven.MavenModule; import hudson.maven.MavenModuleSet; import hudson.maven.MavenModuleSetBuild; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.ItemGroup; import hudson.model.Job; import java.io.IOException; import java.util.logging.Level; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpMethodBase; import org.apache.commons.httpclient.methods.FileRequestEntity; import org.apache.commons.httpclient.methods.PostMethod; /** * {@link Thread} responsible for reading the queue and sending builds. */ public class PublisherThread extends Thread { private AbstractBuild currentRequest = null; private volatile ThreadState state = ThreadState.IDLE; /** * The public Hudson that this thread is publishing to. */ private final HudsonInstance hudsonInstance; /** * @param hudsonInstance */ PublisherThread(HudsonInstance hudsonInstance) { super("Hudson - Build-Publisher Thread for "+hudsonInstance.getName()); this.hudsonInstance = hudsonInstance; } @Override public void run() { try { while (true) { state = ThreadState.IDLE; currentRequest = hudsonInstance.nextRequest(); state = new ThreadState.Publishing(currentRequest); StatusAction.setBuildStatusAction(currentRequest, new StatusInfo(StatusInfo.State.INPROGRESS, "Build is being transmitted", hudsonInstance .getName(), null)); try { // Proceed transmission String publicHudsonUrl = hudsonInstance.getUrl(); AbstractProject project = currentRequest.getProject(); if (project instanceof MatrixConfiguration) { //We can't create remote parent project here (we might collide with another MatrixRun), //just check if it exists... String projectURL = publicHudsonUrl + "job/" + ((MatrixConfiguration) project).getParent().getName(); if (!urlExists(projectURL)) { //...If not, stop here HudsonInstance.LOGGER.log(Level.WARNING, "Build " + currentRequest.getNumber() + " of matrix configuration "+ project.getName() + " couldn't be published: Parent project " + project.getParent().getFullName() + " doesn't exist on the remote instance."); //Since user has to fix the problem first, it makes no sense to add the requeust to the queue immediately hudsonInstance.removeRequest( currentRequest, new StatusInfo(StatusInfo.State.INTERRUPTED, "The parent project doesn't exist on the remote instance." + " Please create it (e.g. by publishing parent matrix build) and try again.", hudsonInstance.getName(), null)); } } else { synchronizeProjectSettings(publicHudsonUrl,project); } hudsonInstance.buildTransmitter.sendBuild(currentRequest, hudsonInstance); //Publish maven module builds if(currentRequest instanceof MavenModuleSetBuild) { for(MavenBuild moduleBuild: ((MavenModuleSetBuild) currentRequest) .getModuleLastBuilds().values()) { hudsonInstance.buildTransmitter.sendBuild(moduleBuild, hudsonInstance); } } //.. and all matrix runs as well else if(currentRequest instanceof MatrixBuild) { for(MatrixRun run: ((MatrixBuild) currentRequest).getRuns()) { if(run != null) { hudsonInstance.publishNewBuild(run); } } } runPostActions(currentRequest); // Notify about success HudsonInstance.LOGGER.info("Build #" + currentRequest.getNumber() + " of project " + currentRequest.getProject().getName() + " was published."); hudsonInstance .removeRequest( currentRequest, new StatusInfo( StatusInfo.State.SUCCESS, "Build transmission was successfully completed", hudsonInstance.getName(), null)); } catch (Exception e) { // Something's wrong. Let's wait awhile and try again. HudsonInstance.LOGGER.log(Level.WARNING,"Error during build transmission: "+e.getMessage(),e); StatusAction.setBuildStatusAction(currentRequest, new StatusInfo(StatusInfo.State.FAILURE_PENDING, "Error during build publishing", hudsonInstance .getName(), e)); hudsonInstance.postponeRequest(currentRequest); HttpMethod httpMethod = null; if (e instanceof ServerFailureException) httpMethod = ((ServerFailureException) e).getMethod(); // TODO make this configurable final long timeout = System.currentTimeMillis() + 1000*60*10; state = new ThreadState.ErrorRecoveryWait(timeout,currentRequest,e,httpMethod); try { while(System.currentTimeMillis() < timeout) Thread.sleep(timeout-System.currentTimeMillis()); } catch (InterruptedException e1) { // note that this also happens when the administrator manually forced a retry, // ignoring timeout HudsonInstance.LOGGER.log(Level.SEVERE,"Build publisher thread was interrupted",e1); } } } } catch(Error e) { state = new ThreadState.Dead(e); throw e; } catch(RuntimeException e) { state = new ThreadState.Dead(e); throw e; } } /** * Gets an immutable object representing what this thread is currently doing. * * @return * never null. */ public ThreadState getCurrentState() { return state; } private void runPostActions(AbstractBuild build) { //run actions that are applicable every time for(PostActionDescriptor descriptor: BuildPublisherPostAction.POST_ACTIONS) { BuildPublisherPostAction action = descriptor.newInstance(); if(action != null) { action.post(build, hudsonInstance); } } //TODO: actions configured per-project //use reflection to mimic project.getPublisher(descriptor)? } /** * Creates new project on the public server in case it doesn't already exist * and submit local config.xml. */ private void synchronizeProjectSettings(String publicHudson, AbstractProject project) throws IOException, ServerFailureException { assertUrlExists(publicHudson); ExternalProjectProperty.applyToProject(project); createOrSynchronize(publicHudson, project); if (project instanceof MavenModuleSet) { //if this is main maven project, synchronize also its modules String parentURL = publicHudson + "job/" + project.getName(); for(MavenModule module: ((MavenModuleSet) project).getItems()) { String moduleModuleSystemName = module .getModuleName().toFileSystemName(); submitConfig(parentURL + "/postBuild/acceptMavenModule?name=" + moduleModuleSystemName, module); } } else if(project instanceof MatrixProject) { //Synchronize active matrix configurarions String parentURL = publicHudson + "job/" + project.getName(); for(MatrixConfiguration configuration: ((MatrixProject) project).getActiveConfigurations()) { submitConfig(parentURL+"/"+ hudson.Util.rawEncode(configuration.getName()) +"/config.xml", configuration); } } else if(project instanceof ItemGroup) { String parentURL = publicHudson + "job/" + project.getName(); for(Object item: ((ItemGroup) project).getItems()) { if(item instanceof Job) { Job job = (Job) item; submitConfig(parentURL+"/"+job.getShortUrl() +"config.xml", job); } } } } private void createOrSynchronize(String publicHudson, AbstractProject project) throws IOException, ServerFailureException { String projectURL = publicHudson + "job/" + project.getName(); String submitConfigUrl; if (!urlExists(projectURL)) { // if the project doesn't exist, create it submitConfigUrl = publicHudson + "createItem?name=" + project.getName(); } else { // otherwise just synchronize config file submitConfigUrl = projectURL + "/config.xml"; } submitConfig(submitConfigUrl, project); } private void submitConfig(String submitConfigUrl, Job project) throws IOException, ServerFailureException { PostMethod method = new PostMethod(); method.setURI(new org.apache.commons.httpclient.URI(submitConfigUrl, false)); method.setRequestEntity(new FileRequestEntity(project.getConfigFile().getFile(),"text/xml")); executeMethod(method); } private void assertUrlExists(String url) throws IOException, ServerFailureException { if (!urlExists(url)) { // wrong address, give up throw new HttpException(url + ": URL doesn't exist"); } } private boolean urlExists(String url) throws ServerFailureException, IOException { PostMethod method = new PostMethod(); method.setURI(new org.apache.commons.httpclient.URI(url,false)); try { executeMethod(method); return true; } catch (ServerFailureException e) { int statusCode = e.getMethod().getStatusCode(); if ((statusCode == 400) || (statusCode == 404)) return false; throw e; } } /* shortcut */ private HttpMethod executeMethod(HttpMethodBase method) throws ServerFailureException { return HTTPBuildTransmitter.executeMethod(method, this.hudsonInstance); } }