/* * Copyright 2016 ThoughtWorks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.thoughtworks.go.domain; import com.thoughtworks.go.util.Clock; import com.thoughtworks.go.util.TimeProvider; import org.joda.time.Duration; import java.io.File; import java.io.Serializable; import java.util.Date; import static com.thoughtworks.go.util.ExceptionUtils.bomb; public class JobInstance extends PersistentObject implements Serializable, Comparable, BuildStateAware, Cloneable { public static final JobInstance NULL = new NullJobInstance(""); private Clock timeProvider = new TimeProvider(); private long stageId = 0; private String name; private JobState state; private JobResult result = JobResult.Unknown; private String agentUuid; private JobStateTransitions stateTransitions = new JobStateTransitions(); private Date scheduledDate; private boolean ignored; private BuildOutputMatcher matcher = new BuildOutputMatcher(); private JobInstanceLog log = new NullJobInstanceLog(); private JobIdentifier identifier; private boolean runOnAllAgents; private boolean runMultipleInstance; private Long originalJobId; private boolean rerun; private boolean pipelineStillConfigured; public JobInstance(String jobConfigName) { this(jobConfigName, new TimeProvider()); } public JobInstance(String jobConfigName, Clock clock) { this.name = jobConfigName; this.timeProvider = clock; schedule(); } public JobInstance(String name, JobInstanceLog log, final Clock clock) { this(name, clock); this.log = log; } /** * @deprecated Only use for IBatis */ public JobInstance() { } public void schedule() { this.scheduledDate = timeProvider.currentTime(); changeState(JobState.Scheduled, this.scheduledDate); } public void reschedule() { schedule(); this.agentUuid = null; this.result = JobResult.Unknown; } public void assign(String agentUuid, Date time) { changeState(JobState.Assigned, time); this.agentUuid = agentUuid; } public void setClock(Clock timeProvider) { this.timeProvider = timeProvider; } @Deprecated //this API is ONLY designed for ibatis. public void setState(JobState state) { this.state = state; } /** * @deprecated the JobInstance should not have a timeProvider. * Please pass in the date and remove this method */ public void changeState(JobState newState) { changeState(newState, timeProvider.currentTime()); } public void changeState(JobState newState, Date stateChangeTime) { if (this.state != newState) { this.state = newState; stateTransitions.add(new JobStateTransition(newState, stateChangeTime)); } } public JobStateTransition getTransition(JobState state) { return stateTransitions.byState(state); } public String getName() { return name; } public JobState getState() { return state; } public JobResult getResult() { return result; } @Deprecated //Only for iBatis & Mothers / Tests public void setResult(JobResult result) { this.result = result; } public String getAgentUuid() { return agentUuid; } public void setAgentUuid(String agentUuid) { this.agentUuid = agentUuid; } @Override public String toString() { return "JobInstance{" + "stageId=" + stageId + ", name='" + name + '\'' + ", state=" + state + ", result=" + result + ", agentUuid='" + agentUuid + '\'' + ", stateTransitions=" + stateTransitions + ", scheduledDate=" + scheduledDate + ", timeProvider=" + timeProvider + ", ignored=" + ignored + ", matcher=" + matcher + ", log=" + log + ", identifier=" + identifier + ", plan=" + plan + ", runOnAllAgents=" + runOnAllAgents + ", runMultipleInstance=" + runMultipleInstance + '}'; } public boolean isNull() { return false; } public boolean isPassed() { return result.isPassed(); } public boolean isCompleted() { return JobState.Completed.equals(getState()); } public boolean isRescheduled() { return JobState.Rescheduled.equals(getState()); } public boolean isPreparing() { return getState().isPreparing(); } public void completing(JobResult result, Date completionDate) { if (isPreparing()) { this.changeState(JobState.Building, completionDate); } this.result = result; this.changeState(JobState.Completing, completionDate); } public void completing(JobResult result) { completing(result, timeProvider.currentTime()); } public int compareTo(Object o) { return getStartedDateFor(JobState.Building) .compareTo(((JobInstance) o).getStartedDateFor(JobState.Building)); } public void setStageId(long stageId) { this.stageId = stageId; } public long getStageId() { return stageId; } public JobStateTransitions getTransitions() { return stateTransitions; } public void setTransitions(JobStateTransitions transitions) { this.stateTransitions = transitions; } public JobState currentStatus() { return state; } public void completed(Date completionDate) { if (result==JobResult.Unknown) { throw new RuntimeException("Result is still unknown. Make sure completing(...) is called and then this if you are doing this through a test."); } this.changeState(JobState.Completed, completionDate); } public boolean isIgnored() { return ignored; } public void setIgnored(boolean ignored) { this.ignored = ignored; } public String displayStatusWithResult() { return state == JobState.Completed ? result.toString().toLowerCase() : state.toString().toLowerCase(); } public JobInstance mostRecentPassed(JobInstance champion) { if (!champion.isPassed()) { return this; } if (!this.isPassed()) { return champion; } return mostRecentCompleted(champion); } public JobInstance mostRecentCompleted(JobInstance champion) { try { if (getCompletedDate() != null && this.moreRecent(champion)) { return this; } } catch (Exception ex) { throw bomb(ex); } return champion; } private boolean moreRecent(JobInstance champion) { return getCompletedDate().compareTo(champion.getCompletedDate()) >= 0; } public void discontinue() { changeState(JobState.Discontinued); } public boolean cancel() { if (!state.isCompleted()) { changeState(JobState.Completed); result = JobResult.Cancelled; return true; } return false; } public String getBuildError() { return log.getBuildError(); } public String getStacktrace() { return log.stacktrace(); } public File getTestIndexPage() { return log.getTestIndexPage(); } public File getServerFailurePage() { return log.getServerFailurePage(); } public void setInstanceLog(JobInstanceLog jobInstanceLog) { this.log = jobInstanceLog; } public boolean isFailed() { return this.getResult().isFailed(); } // Begin Date / Time Related Methods public Long durationOfCompletedBuildInSeconds() { Date buildingDate = getStartedDateFor(JobState.Building); Date completedDate = getCompletedDate(); if (buildingDate == null || completedDate == null) { return 0L; } Long elapsed = completedDate.getTime() - buildingDate.getTime(); int elapsedSeconds = Math.round(elapsed / 1000); return Long.valueOf(elapsedSeconds); } public String getCurrentBuildDuration() { return String.valueOf(elapsedSeconds()); } private Long elapsedSeconds() { if (state.isCompleted()) { return durationOfCompletedBuildInSeconds(); } Date buildingDate = getStartedDateFor(JobState.Building); if (buildingDate != null) { long elapsed = timeProvider.currentTime().getTime() - buildingDate.getTime(); return (long) Math.round(elapsed / 1000); } else { return 0L; } } /** * Scheduled date is duplicated in BUILDINSTANCE table to simplify sql statements. */ public Date getScheduledDate() { return scheduledDate; } @Deprecated //Only for iBatis and BuildInstanceMother. public void setScheduledDate(Date scheduledDate) { this.scheduledDate = scheduledDate; } public Date getStartedDateFor(JobState state) { JobStateTransition transition = this.stateTransitions.byState(state); return transition == null ? null : transition.getStateChangeTime(); } // End Date / Time Related Methods th public JobInstance clone() { try { return (JobInstance) super.clone(); } catch (CloneNotSupportedException e) { return null; } } public void fail() { Date completionDate = timeProvider.currentTime(); completing(JobResult.Failed, completionDate); completed(completionDate); } public String getPipelineName() { return identifier.getPipelineName(); } public Integer getPipelineCounter() { return identifier.getPipelineCounter(); } public String getStageName() { return identifier.getStageName(); } public String getStageCounter() { return identifier.getStageCounter(); } public void setIdentifier(JobIdentifier identifier) { this.identifier = identifier; } public JobIdentifier getIdentifier() { return identifier; } public String getBuildDurationKey(String pipelineName, String stageName) { return String.format("BUILD_DURATION: %s_%s_%s_%s", pipelineName, stageName, getName(), getAgentUuid()); } public boolean isAssignedToAgent() { return getAgentUuid() != null; } //TODO: #2846 this is a temporary hack to enable us to save these objects properly private JobPlan plan; public void setPlan(JobPlan plan) { this.plan = plan; } public JobPlan getPlan() { return plan; } public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } JobInstance instance = (JobInstance) o; if (ignored != instance.ignored) { return false; } if (stageId != instance.stageId) { return false; } if (agentUuid != null ? !agentUuid.equals(instance.agentUuid) : instance.agentUuid != null) { return false; } if (identifier != null ? !identifier.equals(instance.identifier) : instance.identifier != null) { return false; } if (name != null ? !name.equals(instance.name) : instance.name != null) { return false; } if (result != instance.result) { return false; } if (scheduledDate != null ? !scheduledDate.equals(instance.scheduledDate) : instance.scheduledDate != null) { return false; } if (state != instance.state) { return false; } if (stateTransitions != null ? !stateTransitions.equals( instance.stateTransitions) : instance.stateTransitions != null) { return false; } return true; } public int hashCode() { int result1; result1 = (int) (stageId ^ (stageId >>> 32)); result1 = 31 * result1 + (name != null ? name.hashCode() : 0); result1 = 31 * result1 + (state != null ? state.hashCode() : 0); result1 = 31 * result1 + (result != null ? result.hashCode() : 0); result1 = 31 * result1 + (agentUuid != null ? agentUuid.hashCode() : 0); result1 = 31 * result1 + (stateTransitions != null ? stateTransitions.hashCode() : 0); result1 = 31 * result1 + (scheduledDate != null ? scheduledDate.hashCode() : 0); result1 = 31 * result1 + (timeProvider != null ? timeProvider.hashCode() : 0); result1 = 31 * result1 + (ignored ? 1 : 0); result1 = 31 * result1 + (matcher != null ? matcher.hashCode() : 0); result1 = 31 * result1 + (log != null ? log.hashCode() : 0); result1 = 31 * result1 + (identifier != null ? identifier.hashCode() : 0); result1 = 31 * result1 + (plan != null ? plan.hashCode() : 0); return result1; } public String buildLocator() { return identifier.buildLocator(); } public String buildLocatorForDisplay() { return identifier.buildLocatorForDisplay(); } public void setRunOnAllAgents(boolean runOnAllAgents) { this.runOnAllAgents = runOnAllAgents; } public boolean isRunOnAllAgents() { return runOnAllAgents; } public boolean isRunMultipleInstance() { return runMultipleInstance; } public void setRunMultipleInstance(boolean runMultipleInstance) { this.runMultipleInstance = runMultipleInstance; } public boolean matches(JobConfigIdentifier identifier) { if (!getPipelineName().equalsIgnoreCase(identifier.getPipelineName())) { return false; } if (!getStageName().equalsIgnoreCase(identifier.getStageName())) { return false; } return jobType().isInstanceOf(name, true, identifier.getJobName()); } JobType jobType() { if (runOnAllAgents) { return new RunOnAllAgents(); } else if (runMultipleInstance) { return new RunMultipleInstance(); } else { return new SingleJobInstance(); } } public boolean isSameStageConfig(JobInstance other) { return this.getIdentifier().isSameStageConfig(other.getIdentifier()); } public boolean isSamePipelineInstance(JobInstance other) { return (getIdentifier().getPipelineLabel().equals(other.getIdentifier().getPipelineLabel())); } public String getTitle() { return getIdentifier().buildLocatorForDisplay(); } public Date latestTransitionDate() { return stateTransitions.latestTransitionDate(); } public long latestTransitionId() { return stateTransitions.latestTransitionId(); } public Duration getElapsedTime() { return new Duration(elapsedSeconds() * 1000); } public Long getOriginalJobId() { return originalJobId; } public void setOriginalJobId(Long originalJobId) { this.originalJobId = originalJobId; } void resetForCopy() { if (!isCopy()) { setOriginalJobId(getId()); } setId(-1); rerun = false; stateTransitions.resetTransitionIds(); } public boolean isCopy() { return originalJobId != null; } public boolean isRerun() { return rerun; } public void setRerun(boolean rerun) { this.rerun = rerun; } public RunDuration getDuration() { if (!isCompleted()) { return RunDuration.IN_PROGRESS_DURATION; } Date scheduleStartTime = getStartedDateFor(JobState.Scheduled); Date completedTime = getCompletedDate(); return new RunDuration.ActualDuration(new Duration(completedTime.getTime() - scheduleStartTime.getTime())); } public Date getAssignedDate() { return getStartedDateFor(JobState.Assigned); } public Date getCompletedDate() { return getStartedDateFor(JobState.Completed); } public boolean isPipelineStillConfigured() { return pipelineStillConfigured; } public void setPipelineStillConfigured(boolean isConfigured) { pipelineStillConfigured = isConfigured; } }