/*
* 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.addthis.hydra.job;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.addthis.codec.annotations.FieldConfig;
import com.addthis.codec.codables.Codable;
import com.addthis.codec.json.CodecJSON;
import com.addthis.hydra.job.mq.JobKey;
import com.addthis.maljson.JSONObject;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* smallest unit of a job assigned to a host
*/
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE,
setterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties({"readOnlyReplicas", "replicationFactor"})
public final class JobTask implements Codable, Cloneable, Comparable<JobTask> {
private static final Logger log = LoggerFactory.getLogger(JobTask.class);
@FieldConfig private String hostUuid;
@FieldConfig private String jobUuid;
@FieldConfig private int node;
@FieldConfig private int state;
@FieldConfig private int runCount;
@FieldConfig private int starts;
@FieldConfig private long startTime;
@FieldConfig private int errors;
@FieldConfig private long fileCount;
@FieldConfig private long fileBytes;
@FieldConfig private int port;
@FieldConfig private int errorCode;
@FieldConfig private boolean wasStopped;
@FieldConfig private int preFailErrorCode;
@FieldConfig private long input;
@FieldConfig private double meanRate;
@FieldConfig private long totalEmitted;
@FieldConfig private String rebalanceSource;
@FieldConfig private String rebalanceTarget;
@FieldConfig private ArrayList<JobTaskReplica> replicas;
private volatile JobKey jobKey;
public JobTask() {}
// Only used for Testing right now
@VisibleForTesting
public JobTask(String hostUuid, int node, int runCount) {
this.hostUuid = hostUuid;
this.jobUuid = "";
this.node = node;
this.runCount = runCount;
jobKey = new JobKey(jobUuid, node);
}
private static final Set<JobTaskState> nonRunningStates = ImmutableSet.of(JobTaskState.IDLE,
JobTaskState.ERROR,
JobTaskState.ALLOCATED,
JobTaskState.REBALANCE,
JobTaskState.QUEUED_HOST_UNAVAIL,
JobTaskState.QUEUED_NO_SLOT,
JobTaskState.QUEUED);
public boolean isRunning() {
JobTaskState taskState = getState();
return !nonRunningStates.contains(taskState);
}
public void setHostUUID(String uuid) {
hostUuid = uuid;
}
public String getHostUUID() {
return hostUuid;
}
public void setJobUUID(String uuid) {
getJobKey().setJobUuid(uuid);
jobUuid = uuid;
}
public String getJobUUID() {
return jobUuid;
}
public void setTaskID(int id) {
getJobKey().setNodeNumber(id);
node = id;
}
public int getTaskID() {
return node;
}
public JobTaskState getState() {
JobTaskState taskState = JobTaskState.makeState(state);
return taskState == null ? JobTaskState.UNKNOWN : taskState;
}
public boolean setState(JobTaskState state) {
return setState(state, false);
}
public boolean setState(JobTaskState state, boolean force) {
JobTaskState curr = getState();
if (force || curr.canTransition(state)) {
this.state = state.ordinal();
return true;
} else if (state != curr) {
log.warn("[task.setstate] task {} cannot transition {} -> {}",
getTaskID(), curr, state, new Exception("Stack Trace"));
return false;
}
return true;
}
public int getRunCount() {
return runCount;
}
public void setRunCount(int runCount) {
this.runCount = runCount;
}
public int getStarts() {
return starts;
}
public int incrementStarts() {
return ++starts;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long time) {
this.startTime = time;
}
public int getErrors() {
return errors;
}
public int incrementErrors() {
return ++errors;
}
public long getFileCount() {
return fileCount;
}
public void setFileCount(long fileCount) {
this.fileCount = fileCount;
}
public long getByteCount() {
return fileBytes;
}
public void setByteCount(long byteCount) {
this.fileBytes = byteCount;
}
// todo: Can we change the contract so it does not rely on this being mutated?
public List<JobTaskReplica> getReplicas() {
return replicas;
}
public boolean hasReplicaOnHost(String hostUuid) {
for (JobTaskReplica replica : getAllReplicas()) {
if (replica != null && replica.getHostUUID().equals(hostUuid)) {
return true;
}
}
return false;
}
public boolean hasReplicaOnHosts(Collection<String> hostUuids) {
for (String hostUuid : hostUuids) {
if (hasReplicaOnHost(hostUuid)) {
return true;
}
}
return false;
}
public void setReplicas(List<JobTaskReplica> replicas) {
this.replicas = Lists.newArrayList(replicas);
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public JSONObject toJSON() throws Exception {
return CodecJSON.encodeJSON(this);
}
@Override
public String toString() {
return "[JobNode:" + hostUuid + "/" + node + "#" + runCount + "]";
}
@Override
public int compareTo(JobTask o) {
return Integer.valueOf(getTaskID()).compareTo(o.getTaskID());
}
public synchronized JobKey getJobKey() {
if (jobKey == null) {
jobKey = new JobKey(jobUuid, node);
}
return jobKey;
}
public List<JobTaskReplica> getAllReplicas() {
List<JobTaskReplica> replicaList = new ArrayList<>();
if (replicas != null) {
replicaList.addAll(replicas);
}
return replicaList;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public int getPreFailErrorCode() {
return preFailErrorCode;
}
public void setPreFailErrorCode(int code) {
preFailErrorCode = code;
}
public void setWasStopped(boolean wasStopped) {
this.wasStopped = wasStopped;
}
public boolean getWasStopped() {
return wasStopped;
}
public long getInput() {
return input;
}
public void setInput(long input) {
this.input = input;
}
public double getMeanRate() {
return meanRate;
}
public void setMeanRate(double meanRate) {
this.meanRate = meanRate;
}
public long getTotalEmitted() {
return totalEmitted;
}
public void setTotalEmitted(long totalEmitted) {
this.totalEmitted = totalEmitted;
}
public String getRebalanceTarget() {
return rebalanceTarget;
}
public void setRebalanceTarget(@Nullable String rebalanceTarget) {
this.rebalanceTarget = rebalanceTarget;
}
public String getRebalanceSource() {
return rebalanceSource;
}
public void setRebalanceSource(@Nullable String rebalanceSource) {
this.rebalanceSource = rebalanceSource;
}
public Set<String> getAllTaskHosts() {
Set<String> rv = new HashSet<>();
rv.add(hostUuid);
if (getAllReplicas() != null) {
for (JobTaskReplica replica : getAllReplicas()) {
if (replica != null && replica.getHostUUID() != null) {
rv.add(replica.getHostUUID());
}
}
}
return rv;
}
/**
* resets a task's tracking metrics. Used in cases where an existing
* task has had its data scrubbed and is essentially starting fresh
*/
public void resetTaskMetrics() {
starts = 0;
setByteCount(0);
setFileCount(0);
setErrorCode(0);
setRunCount(0);
setInput(0);
setMeanRate(0);
setTotalEmitted(0);
}
public void replaceReplica(String failedHostUuid, String newHostUuid) {
if (getAllReplicas() != null) {
for (JobTaskReplica replica : getAllReplicas()) {
if (failedHostUuid.equals(replica.getHostUUID())) {
replica.setHostUUID(newHostUuid);
}
}
}
}
}