/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.ambari.server.actionmanager;
import org.apache.ambari.server.Role;
import org.apache.ambari.server.RoleCommand;
import org.apache.ambari.server.orm.dao.ExecutionCommandDAO;
import org.apache.ambari.server.orm.dao.HostDAO;
import org.apache.ambari.server.orm.entities.ExecutionCommandEntity;
import org.apache.ambari.server.orm.entities.HostEntity;
import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
import org.apache.ambari.server.state.Host;
import org.apache.ambari.server.state.ServiceComponentHostEvent;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
/**
* This class encapsulates the information for an task on a host for a
* particular role which action manager needs. It doesn't capture actual command
* and parameters, but just the stuff enough for action manager to track the
* request.
* <p/>
* Since this class is cached by the {@link ActionDBAccessor}, it should not
* hold references to JPA entities. It's possible that by holding onto JPA
* entities, they will inadvertently hold onto the entire cache of entities in
* the L1 cache.
*/
public class HostRoleCommand {
private final Role role;
private final ServiceComponentHostEventWrapper event;
private long taskId = -1;
private long stageId = -1;
private long requestId = -1;
private long hostId = -1;
private String hostName;
private HostRoleStatus status = HostRoleStatus.PENDING;
private String stdout = "";
private String stderr = "";
public String outputLog = null;
public String errorLog = null;
private String structuredOut = "";
private int exitCode = 999; //Default is unknown
private long startTime = -1;
private long originalStartTime = -1;
private long endTime = -1;
private long lastAttemptTime = -1;
private short attemptCount = 0;
private final boolean retryAllowed;
private final boolean autoSkipFailure;
private RoleCommand roleCommand;
private String commandDetail;
private String customCommandName;
private ExecutionCommandWrapper executionCommandWrapper;
private boolean isBackgroundCommand = false;
private String opsDisplayName;
@Inject
private ExecutionCommandDAO executionCommandDAO;
@Inject
private HostDAO hostDAO;
@Inject
private ExecutionCommandWrapperFactory ecwFactory;
/**
* Simple constructor, should be created using the Factory class.
* @param hostName Host name
* @param role Action to run
* @param event Event on the host and component
* @param command Type of command
* @param hostDAO {@link org.apache.ambari.server.orm.dao.HostDAO} instance being injected
*/
@AssistedInject
public HostRoleCommand(String hostName, Role role,
ServiceComponentHostEvent event, RoleCommand command, HostDAO hostDAO,
ExecutionCommandDAO executionCommandDAO, ExecutionCommandWrapperFactory ecwFactory) {
this(hostName, role, event, command, false, false, hostDAO, executionCommandDAO, ecwFactory);
}
/**
* Simple constructor, should be created using the Factory class.
* @param hostName Host name
* @param role Action to run
* @param event Event on the host and component
* @param roleCommand Type of command
* @param retryAllowed Whether the command can be repeated
* @param hostDAO {@link org.apache.ambari.server.orm.dao.HostDAO} instance being injected
*/
@AssistedInject
public HostRoleCommand(String hostName, Role role, ServiceComponentHostEvent event,
RoleCommand roleCommand, boolean retryAllowed, boolean autoSkipFailure, HostDAO hostDAO,
ExecutionCommandDAO executionCommandDAO, ExecutionCommandWrapperFactory ecwFactory) {
this.hostDAO = hostDAO;
this.executionCommandDAO = executionCommandDAO;
this.ecwFactory = ecwFactory;
this.role = role;
this.event = new ServiceComponentHostEventWrapper(event);
this.roleCommand = roleCommand;
this.retryAllowed = retryAllowed;
this.autoSkipFailure = autoSkipFailure;
this.hostName = hostName;
HostEntity hostEntity = this.hostDAO.findByName(hostName);
if (null != hostEntity) {
hostId = hostEntity.getHostId();
}
}
@AssistedInject
public HostRoleCommand(Host host, Role role, ServiceComponentHostEvent event,
RoleCommand roleCommand, boolean retryAllowed, boolean autoSkipFailure, HostDAO hostDAO,
ExecutionCommandDAO executionCommandDAO, ExecutionCommandWrapperFactory ecwFactory) {
this.hostDAO = hostDAO;
this.executionCommandDAO = executionCommandDAO;
this.ecwFactory = ecwFactory;
this.role = role;
this.event = new ServiceComponentHostEventWrapper(event);
this.roleCommand = roleCommand;
this.retryAllowed = retryAllowed;
this.autoSkipFailure = autoSkipFailure;
hostId = host.getHostId();
hostName = host.getHostName();
}
@AssistedInject
public HostRoleCommand(@Assisted HostRoleCommandEntity hostRoleCommandEntity, HostDAO hostDAO,
ExecutionCommandDAO executionCommandDAO, ExecutionCommandWrapperFactory ecwFactory) {
this.hostDAO = hostDAO;
this.executionCommandDAO = executionCommandDAO;
this.ecwFactory = ecwFactory;
taskId = hostRoleCommandEntity.getTaskId();
stageId = null != hostRoleCommandEntity.getStageId() ? hostRoleCommandEntity.getStageId()
: hostRoleCommandEntity.getStage().getStageId();
requestId = null != hostRoleCommandEntity.getRequestId() ? hostRoleCommandEntity.getRequestId()
: hostRoleCommandEntity.getStage().getRequestId();
if (null != hostRoleCommandEntity.getHostEntity()) {
hostId = hostRoleCommandEntity.getHostId();
}
hostName = hostRoleCommandEntity.getHostName();
role = hostRoleCommandEntity.getRole();
status = hostRoleCommandEntity.getStatus();
stdout = hostRoleCommandEntity.getStdOut() != null ? new String(hostRoleCommandEntity.getStdOut()) : "";
stderr = hostRoleCommandEntity.getStdError() != null ? new String(hostRoleCommandEntity.getStdError()) : "";
outputLog = hostRoleCommandEntity.getOutputLog();
errorLog = hostRoleCommandEntity.getErrorLog();
structuredOut = hostRoleCommandEntity.getStructuredOut() != null ? new String(hostRoleCommandEntity.getStructuredOut()) : "";
exitCode = hostRoleCommandEntity.getExitcode();
startTime = hostRoleCommandEntity.getStartTime() != null ? hostRoleCommandEntity.getStartTime() : -1L;
originalStartTime = hostRoleCommandEntity.getOriginalStartTime() != null ? hostRoleCommandEntity.getOriginalStartTime() : -1L;
endTime = hostRoleCommandEntity.getEndTime() != null ? hostRoleCommandEntity.getEndTime() : -1L;
lastAttemptTime = hostRoleCommandEntity.getLastAttemptTime() != null ? hostRoleCommandEntity.getLastAttemptTime() : -1L;
attemptCount = hostRoleCommandEntity.getAttemptCount();
retryAllowed = hostRoleCommandEntity.isRetryAllowed();
autoSkipFailure = hostRoleCommandEntity.isFailureAutoSkipped();
roleCommand = hostRoleCommandEntity.getRoleCommand();
event = new ServiceComponentHostEventWrapper(hostRoleCommandEntity.getEvent());
commandDetail = hostRoleCommandEntity.getCommandDetail();
opsDisplayName = hostRoleCommandEntity.getOpsDisplayName();
customCommandName = hostRoleCommandEntity.getCustomCommandName();
isBackgroundCommand = hostRoleCommandEntity.isBackgroundCommand();
}
//todo: why is this not symmetrical with the constructor which takes an entity
//todo: why are we only setting some fields in this constructor, 8 fields missing?????
public HostRoleCommandEntity constructNewPersistenceEntity() {
HostRoleCommandEntity hostRoleCommandEntity = new HostRoleCommandEntity();
hostRoleCommandEntity.setRole(role);
hostRoleCommandEntity.setStatus(status);
hostRoleCommandEntity.setStdError(stderr.getBytes());
hostRoleCommandEntity.setExitcode(exitCode);
hostRoleCommandEntity.setStdOut(stdout.getBytes());
hostRoleCommandEntity.setStructuredOut(structuredOut.getBytes());
hostRoleCommandEntity.setStartTime(startTime);
hostRoleCommandEntity.setOriginalStartTime(originalStartTime);
hostRoleCommandEntity.setEndTime(endTime);
hostRoleCommandEntity.setLastAttemptTime(lastAttemptTime);
hostRoleCommandEntity.setAttemptCount(attemptCount);
hostRoleCommandEntity.setRetryAllowed(retryAllowed);
hostRoleCommandEntity.setAutoSkipOnFailure(autoSkipFailure);
hostRoleCommandEntity.setRoleCommand(roleCommand);
hostRoleCommandEntity.setCommandDetail(commandDetail);
hostRoleCommandEntity.setOpsDisplayName(opsDisplayName);
hostRoleCommandEntity.setCustomCommandName(customCommandName);
hostRoleCommandEntity.setBackgroundCommand(isBackgroundCommand);
HostEntity hostEntity = hostDAO.findById(hostId);
if (null != hostEntity) {
hostRoleCommandEntity.setHostEntity(hostEntity);
}
hostRoleCommandEntity.setEvent(event.getEventJson());
// set IDs if the wrapping object has them - they are most likely
// non-updatable in JPA since they are retrieved from a relationship,
// however the JPA cache may choose to not refresh the entity so they would
// end up being null if not set before persisting the command entity
if (requestId >= 0) {
hostRoleCommandEntity.setRequestId(requestId);
}
if (stageId >= 0) {
hostRoleCommandEntity.setStageId(stageId);
}
if (taskId >= 0) {
hostRoleCommandEntity.setTaskId(taskId);
}
return hostRoleCommandEntity;
}
ExecutionCommandEntity constructExecutionCommandEntity() {
ExecutionCommandEntity executionCommandEntity = new ExecutionCommandEntity();
executionCommandEntity.setCommand(executionCommandWrapper.getJson().getBytes());
return executionCommandEntity;
}
public long getTaskId() {
return taskId;
}
public void setRequestId(long requestId) {
this.requestId = requestId;
}
public void setStageId(long stageId) {
this.stageId = stageId;
}
public void setTaskId(long taskId) {
if (this.taskId != -1) {
throw new RuntimeException("Attempt to set taskId again, not allowed");
}
this.taskId = taskId;
//todo: do we need to have a wrapper? This invariant isn't enforced in constructor.
//todo: for now, I am just going to wrap this in a null check
if (executionCommandWrapper != null) {
executionCommandWrapper.getExecutionCommand().setTaskId(taskId);
//Need to invalidate json because taskId is updated.
executionCommandWrapper.invalidateJson();
}
}
/**
* Sets the host ID and name for the host associated with this command.
*
* @param hostId
* @param hostName
*/
public void setHost(long hostId, String hostName) {
this.hostId = hostId;
this.hostName = hostName;
}
public String getHostName() {
return hostName;
}
public long getHostId() {
return hostId;
}
public Role getRole() {
return role;
}
public String getCommandDetail() {
return commandDetail;
}
public void setCommandDetail(String commandDetail) {
this.commandDetail = commandDetail;
}
public String getOpsDisplayName() {
return opsDisplayName;
}
public void setOpsDisplayName(String opsDisplayName) {
this.opsDisplayName = opsDisplayName;
}
public String getCustomCommandName() {
return customCommandName;
}
public void setCustomCommandName(String customCommandName) {
this.customCommandName = customCommandName;
}
public HostRoleStatus getStatus() {
return status;
}
public void setStatus(HostRoleStatus status) {
this.status = status;
}
public ServiceComponentHostEventWrapper getEvent() {
return event;
}
public String getStdout() {
return stdout;
}
public void setStdout(String stdout) {
this.stdout = stdout;
}
public String getStderr() {
return stderr;
}
public void setStderr(String stderr) {
this.stderr = stderr;
}
public String getOutputLog() { return outputLog; }
public void setOutputLog(String outputLog) {
this.outputLog = outputLog;
}
public String getErrorLog() { return errorLog; }
public void setErrorLog(String errorLog) {
this.errorLog = errorLog;
}
public int getExitCode() {
return exitCode;
}
public void setExitCode(int exitCode) {
this.exitCode = exitCode;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public long getOriginalStartTime() {
return originalStartTime;
}
public void setOriginalStartTime(long originalStartTime) {
this.originalStartTime = originalStartTime;
}
public long getLastAttemptTime() {
return lastAttemptTime;
}
public void setLastAttemptTime(long lastAttemptTime) {
this.lastAttemptTime = lastAttemptTime;
}
public short getAttemptCount() {
return attemptCount;
}
public void incrementAttemptCount() {
attemptCount++;
}
public boolean isRetryAllowed() {
return retryAllowed;
}
public String getStructuredOut() {
return structuredOut;
}
public void setStructuredOut(String structuredOut) {
this.structuredOut = structuredOut;
}
public long getEndTime() {
return endTime;
}
public void setEndTime(long endTime) {
this.endTime = endTime;
}
public ExecutionCommandWrapper getExecutionCommandWrapper() {
if (taskId != -1 && executionCommandWrapper == null) {
ExecutionCommandEntity commandEntity = executionCommandDAO.findByPK(taskId);
if (commandEntity == null) {
throw new RuntimeException("Invalid DB state, broken one-to-one relation for taskId=" + taskId);
}
executionCommandWrapper = ecwFactory.createFromJson(new String(commandEntity.getCommand()));
}
return executionCommandWrapper;
}
public void setExecutionCommandWrapper(ExecutionCommandWrapper executionCommandWrapper) {
this.executionCommandWrapper = executionCommandWrapper;
}
public RoleCommand getRoleCommand() {
return roleCommand;
}
public void setRoleCommand(RoleCommand roleCommand) {
this.roleCommand = roleCommand;
}
public long getStageId() {
return stageId;
}
public long getRequestId() {
return requestId;
}
/**
* Gets whether this command runs in the background and does not block other
* commands.
*
* @return {@code true} if this command runs in the background, {@code false}
* otherise.
*/
public boolean isBackgroundCommand() {
return isBackgroundCommand;
}
/**
* Sets whether this command runs in the background and does not block other
* commands.
*
* @param isBackgroundCommand
* {@code true} if this command runs in the background, {@code false}
* otherise.
*/
public void setBackgroundCommand(boolean isBackgroundCommand) {
this.isBackgroundCommand = isBackgroundCommand;
}
/**
* Gets whether commands which fail and are retryable are automatically
* skipped and marked with {@link HostRoleStatus#SKIPPED_FAILED}.
*
* @return
*/
public boolean isFailureAutoSkipped() {
return autoSkipFailure;
}
@Override
public int hashCode() {
return Long.valueOf(taskId).hashCode();
}
@Override
public boolean equals(Object other) {
if (!(other instanceof HostRoleCommand)) {
return false;
}
HostRoleCommand o = (HostRoleCommand) other;
return hashCode() == o.hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("HostRoleCommand State:\n");
builder.append(" TaskId: ").append(taskId).append("\n");
builder.append(" Role: ").append(role).append("\n");
builder.append(" Status: ").append(status).append("\n");
builder.append(" Event: ").append(event).append("\n");
builder.append(" RetryAllowed: ").append(retryAllowed).append("\n");
builder.append(" AutoSkipFailure: ").append(autoSkipFailure).append("\n");
builder.append(" Output log: ").append(outputLog).append("\n");
builder.append(" Error log: ").append(errorLog).append("\n");
builder.append(" stdout: ").append(stdout).append("\n");
builder.append(" stderr: ").append(stderr).append("\n");
builder.append(" exitcode: ").append(exitCode).append("\n");
builder.append(" Start time: ").append(startTime).append("\n");
builder.append(" Original Start time: ").append(originalStartTime).append("\n");
builder.append(" Last attempt time: ").append(lastAttemptTime).append("\n");
builder.append(" attempt count: ").append(attemptCount).append("\n");
return builder.toString();
}
}