/*
* 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.mq;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import com.addthis.hydra.minion.Minion;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonIgnoreProperties({"messageType", "totalLive", "readOnly"})
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, defaultImpl = HostState.class)
public class HostState implements HostMessage {
@JsonProperty private String host;
@JsonProperty private int port;
@JsonProperty private String uuid;
@JsonProperty private String user;
@JsonProperty private String path;
@JsonProperty private String group;
// Host State is determined by others zk group membership, by
// definition it can not persist.
@JsonProperty private boolean up;
@JsonProperty private long time;
@JsonProperty private long uptime;
@JsonProperty private int availableTaskSlots;
@JsonProperty private int maxTaskSlots;
@JsonProperty private JobKey[] running;
@JsonProperty private JobKey[] replicating;
@JsonProperty private JobKey[] backingup;
@JsonProperty private JobKey[] stopped;
@JsonProperty private JobKey[] replicas;
@JsonProperty private JobKey[] incompleteReplicas;
@JsonProperty private JobKey[] queued;
@JsonProperty private HostCapacity used;
@JsonProperty private HostCapacity max;
@JsonProperty private boolean dead;
@JsonProperty private long lastUpdateTime;
@JsonProperty private double histQueueSize;
@JsonProperty private double histWaitTime;
//TODO: remove but need this in for now because de-serialization fails without it
@JsonProperty private HashMap<String, Double> jobRuntimes = new HashMap<>();
@JsonProperty private boolean diskReadOnly;
@JsonProperty private boolean disabled;
@JsonProperty private double meanActiveTasks;
@JsonProperty private String minionTypes;
// Do not encode this derived, internal, non-typesafe field
private HashMap<String, Integer> jobTaskCountMap;
@JsonCreator
private HostState() {}
public HostState(String hostUuid) {
this.uuid = hostUuid;
}
@Override
public String getHostUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
@JsonProperty
public void setHostUuid(String uuid) {
this.uuid = uuid;
}
public void setUpdated() {
lastUpdateTime = System.currentTimeMillis();
}
public boolean hasLive(@Nullable JobKey jobKey) {
if (stopped != null && Arrays.asList(stopped).contains(jobKey)) {
return true;
}
if (queued != null && Arrays.asList(queued).contains(jobKey)) {
return true;
}
if (running != null && Arrays.asList(running).contains(jobKey)) {
return true;
}
if (replicating != null && Arrays.asList(replicating).contains(jobKey)) {
return true;
}
if (backingup != null && Arrays.asList(backingup).contains(jobKey)) {
return true;
}
return false;
}
public boolean hasIncompleteReplica(JobKey jobKey) {
return (incompleteReplicas != null) && Arrays.asList(incompleteReplicas).contains(jobKey);
}
public List<JobKey> allJobKeys() {
List<JobKey> rv = new ArrayList<>();
for (JobKey[] jobKeys : Arrays.asList(stopped, queued, running, replicating, backingup, replicas)) {
if (jobKeys != null) {
rv.addAll(Arrays.asList(jobKeys));
}
}
return rv;
}
public Integer addJob(String jobId) {
if (this.jobTaskCountMap == null) {
jobTaskCountMap = new HashMap<>();
}
int currentCount = 0;
if (jobTaskCountMap.containsKey(jobId)) {
currentCount = jobTaskCountMap.get(jobId);
}
return jobTaskCountMap.put(jobId, currentCount + 1);
}
public void generateJobTaskCountMap() {
jobTaskCountMap = new HashMap<>();
List<JobKey[]> activeJobsSources = Arrays.asList(stopped, queued, running, replicas);
for (JobKey[] source : activeJobsSources) {
if (source == null) {
continue;
}
for (JobKey jobKey : source) {
if (jobKey == null) {
continue;
}
Integer oldCount = jobTaskCountMap.get(jobKey.getJobUuid());
int newCount;
if (oldCount != null) {
newCount = 1 + oldCount;
} else {
newCount = 1;
}
jobTaskCountMap.put(jobKey.getJobUuid(), newCount);
}
}
}
public Integer getTaskCount(String jobId) {
if (jobTaskCountMap == null) {
generateJobTaskCountMap();
}
if ((jobTaskCountMap != null) && jobTaskCountMap.containsKey(jobId)) {
return jobTaskCountMap.get(jobId);
} else {
return 0;
}
}
public boolean canMirrorTasks() {
return up && !dead && !diskReadOnly && !disabled;
}
// share at least one type; imperfect host pruning
public boolean hasType(String externalType) {
if (minionTypes == null) {
minionTypes = Minion.defaultMinionType;
}
Set<String> externalTypes = HostState.typeStringToSet(externalType);
Set<String> myTypes = HostState.typeStringToSet(minionTypes);
return !Sets.intersection(externalTypes, myTypes).isEmpty();
}
private static Set<String> typeStringToSet(String typeString) {
if (typeString.contains(",")) {
return Sets.newHashSet(typeString.split(","));
} else {
return Collections.singleton(typeString);
}
}
public int countTotalLive() {
int total = 0;
for (JobKey[] keys : Arrays.asList(stopped, running, replicating, backingup, queued)) {
if (keys != null) {
total += keys.length;
}
}
return total;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("uuid", getHostUuid())
.add("last-update-time", getLastUpdateTime())
.add("host", getHost())
.add("port", getPort())
.add("group", getGroup())
.add("time", getTime())
.add("uptime", getUptime())
// You probably only want to print these out when testing locally
// .add("running", getRunning())
// .add("stopped", getStopped())
// .add("replicas", getReplicas())
// .add("queued",getQueued())
.add("used", getUsed())
.add("user", getUser())
.add("path", getPath())
.add("max", getMax())
.add("up", isUp())
.add("dead", isDead())
.add("diskReadOnly", isDiskReadOnly())
.toString();
}
//
// generic getters / setters
//
public long getLastUpdateTime() {
return lastUpdateTime;
}
public void setLastUpdateTime(long lastUpdateTime) {
this.lastUpdateTime = lastUpdateTime;
}
public double getHistQueueSize() {
return histQueueSize;
}
public void setHistQueueSize(double size) {
this.histQueueSize = size;
}
public double getHistWaitTime() {
return histWaitTime;
}
public void setHistWaitTime(double seconds) {
this.histWaitTime = seconds;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public boolean isUp() {
return up;
}
public void setUp(boolean up) {
this.up = up;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public long getUptime() {
return uptime;
}
public void setUptime(long uptime) {
this.uptime = uptime;
}
public JobKey[] getRunning() {
return running;
}
public void setRunning(JobKey[] running) {
this.running = running;
}
// Needed for serialization!
public JobKey[] getReplicating() {
return replicating;
}
public void setReplicating(JobKey[] replicating) {
this.replicating = replicating;
}
// Needed for serialization!
public JobKey[] getBackingup() {
return backingup;
}
public void setBackingup(JobKey[] backingup) {
this.backingup = backingup;
}
public void setReplicas(JobKey[] replicas) {
this.replicas = replicas;
}
public JobKey[] getReplicas() {
return replicas;
}
public void setStopped(JobKey[] stopped) {
this.stopped = stopped;
}
public JobKey[] getStopped() {
return stopped;
}
public JobKey[] getQueued() {
return queued;
}
public void setQueued(JobKey[] queued) {
this.queued = queued;
}
public HostCapacity getUsed() {
return used;
}
public void setUsed(HostCapacity used) {
this.used = used;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public HostCapacity getMax() {
return max;
}
public void setMax(HostCapacity max) {
this.max = max;
}
public long getAvailDiskBytes() {
if ((max == null) || (used == null)) {
return 1; // Fix some tests
}
return max.getDisk() - used.getDisk();
}
public double getDiskUsedPercent() {
return getDiskUsedPercentModified(0);
}
public double getDiskUsedPercentModified(long reduce) {
Preconditions.checkArgument(reduce >= 0, "reduce must be non-negative");
if ((max == null) || (used == null) || (max.getDisk() <= 0)) {
return 0;
}
double value = (double) used.getDisk() / (double) (max.getDisk() - reduce);
if ((value < 0) || (value > 1)) {
return 1;
}
return value;
}
public void setDead(boolean dead) {
this.dead = dead;
}
public boolean isDead() {
return dead;
}
public boolean isDiskReadOnly() {
return this.diskReadOnly;
}
public void setDiskReadOnly(boolean diskReadOnly) {
this.diskReadOnly = diskReadOnly;
}
public int getAvailableTaskSlots() {
return availableTaskSlots;
}
public void setAvailableTaskSlots(int availableTaskSlots) {
this.availableTaskSlots = availableTaskSlots;
}
public boolean isDisabled() {
return disabled;
}
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
public double getMeanActiveTasks() {
return meanActiveTasks;
}
public void setMeanActiveTasks(double meanActiveTasks) {
this.meanActiveTasks = meanActiveTasks;
}
public String getMinionTypes() {
return minionTypes;
}
public void setMinionTypes(String minionTypes) {
this.minionTypes = minionTypes;
}
public JobKey[] getIncompleteReplicas() {
return incompleteReplicas;
}
public void setIncompleteReplicas(JobKey[] incompleteReplicas) {
this.incompleteReplicas = incompleteReplicas;
}
public int getMaxTaskSlots() {
return maxTaskSlots;
}
public void setMaxTaskSlots(int maxTaskSlots) {
this.maxTaskSlots = maxTaskSlots;
}
}