content: /*
   * 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();
    }
  }
  