/*
* 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.orm.dao;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.metamodel.SingularAttribute;
import org.apache.ambari.server.actionmanager.ActionManager;
import org.apache.ambari.server.actionmanager.HostRoleStatus;
import org.apache.ambari.server.api.query.JpaPredicateVisitor;
import org.apache.ambari.server.api.query.JpaSortBuilder;
import org.apache.ambari.server.controller.internal.CalculatedStatus;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.spi.Request;
import org.apache.ambari.server.controller.utilities.PredicateHelper;
import org.apache.ambari.server.orm.RequiresSession;
import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
import org.apache.ambari.server.orm.entities.StageEntity;
import org.apache.ambari.server.orm.entities.StageEntityPK;
import org.apache.ambari.server.orm.entities.StageEntity_;
import org.apache.ambari.server.utils.StageUtils;
import org.eclipse.persistence.config.HintValues;
import org.eclipse.persistence.config.QueryHints;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.persist.Transactional;
@Singleton
public class StageDAO {
/**
* Mapping of valid status transitions that that are driven by manual input.
*/
private static Map<HostRoleStatus, EnumSet<HostRoleStatus>> manualTransitionMap = new HashMap<>();
static {
manualTransitionMap.put(HostRoleStatus.HOLDING,
EnumSet.of(HostRoleStatus.COMPLETED, HostRoleStatus.ABORTED));
manualTransitionMap.put(HostRoleStatus.HOLDING_FAILED,
EnumSet.of(HostRoleStatus.PENDING, HostRoleStatus.FAILED, HostRoleStatus.ABORTED));
manualTransitionMap.put(HostRoleStatus.HOLDING_TIMEDOUT,
EnumSet.of(HostRoleStatus.PENDING, HostRoleStatus.TIMEDOUT, HostRoleStatus.ABORTED));
// todo: perhaps add a CANCELED status that just affects a stage and wont
// abort the request
// todo: so, if I scale 10 nodes and actually provision 5 and then later
// decide I don't want those
// todo: additional 5 nodes I can cancel them and the corresponding request
// will have a status of COMPLETED
}
@Inject
Provider<EntityManager> entityManagerProvider;
@Inject
DaoUtils daoUtils;
@Inject
HostRoleCommandDAO hostRoleCommandDao;
@RequiresSession
public StageEntity findByPK(StageEntityPK stageEntityPK) {
return entityManagerProvider.get().find(StageEntity.class, stageEntityPK);
}
@RequiresSession
public List<StageEntity> findAll() {
return daoUtils.selectAll(entityManagerProvider.get(), StageEntity.class);
}
@RequiresSession
public long getLastRequestId() {
TypedQuery<Long> query = entityManagerProvider.get().createQuery("SELECT max(stage.requestId) FROM StageEntity stage", Long.class);
Long result = daoUtils.selectSingle(query);
if (result != null) {
return result;
} else {
return 0;
}
}
@RequiresSession
public StageEntity findByActionId(String actionId) {
long[] ids = StageUtils.getRequestStage(actionId);
StageEntityPK pk = new StageEntityPK();
pk.setRequestId(ids[0]);
pk.setStageId(ids[1]);
return findByPK(pk);
}
@RequiresSession
public List<StageEntity> findByRequestId(long requestId) {
TypedQuery<StageEntity> query = entityManagerProvider.get().createQuery("SELECT stage " +
"FROM StageEntity stage " +
"WHERE stage.requestId=?1 " +
"ORDER BY stage.stageId", StageEntity.class);
return daoUtils.selectList(query, requestId);
}
@RequiresSession
public List<StageEntity> findByRequestIdAndCommandStatuses(Long requestId, Collection<HostRoleStatus> statuses) {
TypedQuery<StageEntity> query = entityManagerProvider.get().createNamedQuery(
"StageEntity.findByRequestIdAndCommandStatuses", StageEntity.class);
query.setParameter("requestId", requestId);
query.setParameter("statuses", statuses);
return daoUtils.selectList(query);
}
/**
* Finds the first stage matching any of the specified statuses for every
* request. For example, to find the first {@link HostRoleStatus#IN_PROGRESS}
* stage for every request, pass in
* {@link HostRoleStatus#IN_PROGRESS_STATUSES}.
*
* @param statuses
* {@link HostRoleStatus}
* @return the list of the first matching stage for the given statuses for
* every request.
*/
@RequiresSession
public List<StageEntity> findFirstStageByStatus(Collection<HostRoleStatus> statuses) {
TypedQuery<Object[]> query = entityManagerProvider.get().createNamedQuery(
"StageEntity.findFirstStageByStatus", Object[].class);
query.setParameter("statuses", statuses);
List<Object[]> results = daoUtils.selectList(query);
List<StageEntity> stages = new ArrayList<>();
for (Object[] result : results) {
StageEntityPK stagePK = new StageEntityPK();
stagePK.setRequestId((Long) result[0]);
stagePK.setStageId((Long) result[1]);
StageEntity stage = findByPK(stagePK);
stages.add(stage);
}
return stages;
}
@RequiresSession
public Map<Long, String> findRequestContext(List<Long> requestIds) {
Map<Long, String> resultMap = new HashMap<>();
if (requestIds != null && !requestIds.isEmpty()) {
TypedQuery<StageEntity> query = entityManagerProvider.get()
.createQuery("SELECT stage FROM StageEntity stage WHERE " +
"stage.requestId IN (SELECT DISTINCT s.requestId FROM StageEntity s " +
"WHERE s.requestId IN ?1)", StageEntity.class);
List<StageEntity> result = daoUtils.selectList(query, requestIds);
if (result != null && !result.isEmpty()) {
for (StageEntity entity : result) {
resultMap.put(entity.getRequestId(), entity.getRequestContext());
}
}
}
return resultMap;
}
@RequiresSession
public String findRequestContext(long requestId) {
TypedQuery<String> query = entityManagerProvider.get().createQuery(
"SELECT stage.requestContext " + "FROM StageEntity stage " +
"WHERE stage.requestId=?1", String.class);
String result = daoUtils.selectOne(query, requestId);
if (result != null) {
return result;
}
else {
return ""; // Since it is defined as empty string in the StageEntity
}
}
@Transactional
public void create(StageEntity stageEntity) {
entityManagerProvider.get().persist(stageEntity);
}
@Transactional
public StageEntity merge(StageEntity stageEntity) {
return entityManagerProvider.get().merge(stageEntity);
}
@Transactional
public void remove(StageEntity stageEntity) {
entityManagerProvider.get().remove(merge(stageEntity));
}
@Transactional
public void removeByPK(StageEntityPK stageEntityPK) {
remove(findByPK(stageEntityPK));
}
/**
* Finds all {@link org.apache.ambari.server.orm.entities.StageEntity} that match the provided
* {@link org.apache.ambari.server.controller.spi.Predicate}. This method will make JPA do the heavy lifting
* of providing a slice of the result set.
*
* @param request
* @return
*/
@RequiresSession
public List<StageEntity> findAll(Request request, Predicate predicate) {
EntityManager entityManager = entityManagerProvider.get();
// convert the Ambari predicate into a JPA predicate
StagePredicateVisitor visitor = new StagePredicateVisitor();
PredicateHelper.visit(predicate, visitor);
CriteriaQuery<StageEntity> query = visitor.getCriteriaQuery();
javax.persistence.criteria.Predicate jpaPredicate = visitor.getJpaPredicate();
if (jpaPredicate != null) {
query.where(jpaPredicate);
}
// sorting
JpaSortBuilder<StageEntity> sortBuilder = new JpaSortBuilder<>();
List<Order> sortOrders = sortBuilder.buildSortOrders(request.getSortRequest(), visitor);
query.orderBy(sortOrders);
TypedQuery<StageEntity> typedQuery = entityManager.createQuery(query);
// !!! https://bugs.eclipse.org/bugs/show_bug.cgi?id=398067
// ensure that an associated entity with a JOIN is not stale; this causes
// the associated StageEntity to be stale
typedQuery.setHint(QueryHints.REFRESH, HintValues.TRUE);
return daoUtils.selectList(typedQuery);
}
/**
* Update the given stage entity with the desired status.
*
* @param stage
* the stage entity to update
* @param desiredStatus
* the desired stage status
* @param actionManager
* the action manager
*
* @throws java.lang.IllegalArgumentException
* if the transition to the desired status is not a legal transition
*/
@Transactional
public void updateStageStatus(StageEntity stage, HostRoleStatus desiredStatus,
ActionManager actionManager) {
Collection<HostRoleCommandEntity> tasks = stage.getHostRoleCommands();
HostRoleStatus currentStatus = CalculatedStatus.statusFromTaskEntities(tasks,
stage.isSkippable()).getStatus();
if (!isValidManualTransition(currentStatus, desiredStatus)) {
throw new IllegalArgumentException(
"Can not transition a stage from " + currentStatus + " to " + desiredStatus);
}
if (desiredStatus == HostRoleStatus.ABORTED) {
actionManager.cancelRequest(stage.getRequestId(), "User aborted.");
} else {
List <HostRoleCommandEntity> hrcWithChangedStatus = new ArrayList<>();
for (HostRoleCommandEntity hostRoleCommand : tasks) {
HostRoleStatus hostRoleStatus = hostRoleCommand.getStatus();
if (hostRoleStatus.equals(currentStatus)) {
hrcWithChangedStatus.add(hostRoleCommand);
hostRoleCommand.setStatus(desiredStatus);
if (desiredStatus == HostRoleStatus.PENDING) {
hostRoleCommand.setStartTime(-1L);
}
hostRoleCommandDao.merge(hostRoleCommand);
}
}
}
}
/**
*
* @param stageEntityPK {@link StageEntityPK}
* @param status {@link HostRoleStatus}
* @param displayStatus {@link HostRoleStatus}
*/
@Transactional
public void updateStatus(StageEntityPK stageEntityPK, HostRoleStatus status, HostRoleStatus displayStatus) {
StageEntity stageEntity = findByPK(stageEntityPK);
stageEntity.setStatus(status);
stageEntity.setDisplayStatus(displayStatus);
merge(stageEntity);
}
/**
* Determine whether or not it is valid to transition from this stage status
* to the given status.
*
* @param status
* the stage status being transitioned to
*
* @return true if it is valid to transition to the given stage status
*/
private static boolean isValidManualTransition(HostRoleStatus status,
HostRoleStatus desiredStatus) {
EnumSet<HostRoleStatus> stageStatusSet = manualTransitionMap.get(status);
return stageStatusSet != null && stageStatusSet.contains(desiredStatus);
}
/**
* The {@link org.apache.ambari.server.orm.dao.StageDAO.StagePredicateVisitor} is used to convert an Ambari
* {@link org.apache.ambari.server.controller.spi.Predicate} into a JPA {@link javax.persistence.criteria.Predicate}.
*/
private final class StagePredicateVisitor extends
JpaPredicateVisitor<StageEntity> {
/**
* Constructor.
*
*/
public StagePredicateVisitor() {
super(entityManagerProvider.get(), StageEntity.class);
}
/**
* {@inheritDoc}
*/
@Override
public Class<StageEntity> getEntityClass() {
return StageEntity.class;
}
/**
* {@inheritDoc}
*/
@Override
public List<? extends SingularAttribute<?, ?>> getPredicateMapping(
String propertyId) {
return StageEntity_.getPredicateMapping().get(propertyId);
}
}
}