/************************************************************************* * Copyright 2009-2014 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. ************************************************************************/ package com.eucalyptus.cloudformation.entity; import com.eucalyptus.cloudformation.AlreadyExistsException; import com.eucalyptus.cloudformation.CloudFormationException; import com.eucalyptus.cloudformation.InternalFailureException; import com.eucalyptus.cloudformation.UpdateStackType; import com.eucalyptus.cloudformation.ValidationErrorException; import com.eucalyptus.cloudformation.template.Template; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionResource; import com.google.common.collect.Lists; import org.apache.log4j.Logger; import org.hibernate.Criteria; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.Restrictions; import java.util.Date; import java.util.List; /** * Created by ethomas on 12/18/13. */ public class StackEntityManager { private static final Class STACK_ENTITY_TRANSACTION_CLASS = StackEntity.class; // We use one object for transactions on both StackEntity and PastStackEntity private static final List<Class<? extends VersionedStackEntity>> ALL_STACK_ENTITY_CLASSES = Lists.newArrayList(StackEntity.class, PastStackEntity.class); public synchronized static void rollbackUpdateStack(String stackId, String accountId, int rolledBackStackVersion) throws CloudFormationException { StackUpdateInfoEntity stackUpdateInfoEntity = StackUpdateInfoEntityManager.getStackUpdateInfoEntity(stackId, accountId); if (stackUpdateInfoEntity == null) { throw new ValidationErrorException("No stack update info entity found"); } if (!stackUpdateInfoEntity.getHasCalledRollbackStackState()) { int updatedStackVersion = rolledBackStackVersion - 1; // version passed in is 1 more than the current updated stack version try (TransactionResource db = Entities.transactionFor(STACK_ENTITY_TRANSACTION_CLASS)) { // Get current stack Criteria criteria = Entities.createCriteria(StackEntity.class) .add(Restrictions.eq("stackId", stackId)) .add(Restrictions.eq("accountId", accountId)) .add(Restrictions.eq("stackVersion", updatedStackVersion)) .add(Restrictions.eq("recordDeleted", Boolean.FALSE)); List<StackEntity> entityList = criteria.list(); if (entityList == null || entityList.isEmpty()) { throw new ValidationErrorException("Stack does not exist"); } else if (entityList.size() > 1) { throw new InternalFailureException("More than one stack exists with this id " + stackId + " and version " + updatedStackVersion); } StackEntity currentStackEntity = entityList.get(0); // Get previous stack Criteria pastCriteria = Entities.createCriteria(PastStackEntity.class) .add(Restrictions.eq("stackId", stackId)) .add(Restrictions.eq("accountId", accountId)) .add(Restrictions.eq("stackVersion", updatedStackVersion - 1)) .add(Restrictions.eq("recordDeleted", Boolean.FALSE)); List<PastStackEntity> pastEntityList = pastCriteria.list(); if (pastEntityList == null || pastEntityList.isEmpty()) { throw new ValidationErrorException("Previous version of stack does not exist with version " + (updatedStackVersion - 1)); } else if (entityList.size() > 1) { throw new InternalFailureException("More than one stack exists with this id " + stackId + " and version " + (updatedStackVersion - 1)); } PastStackEntity pastStackEntity = pastEntityList.get(0); // Basically swap the two entries. swapStackEntityFields(pastStackEntity, currentStackEntity); // set the versions accordingly currentStackEntity.setStackVersion(rolledBackStackVersion); pastStackEntity.setStackVersion(updatedStackVersion); // the past stack entry now has the current status, so update that at least currentStackEntity.setStackStatus(pastStackEntity.getStackStatus()); currentStackEntity.setStackStatusReason(pastStackEntity.getStackStatusReason()); db.commit(); StackUpdateInfoEntityManager.setHasCalledRollbackStackState(stackId, accountId); } } } public static void swapStackEntityFields(VersionedStackEntity s1, VersionedStackEntity s2) { VersionedStackEntity temp = new StackEntity(); // could be any subtype, it really doesn't matter. copyStackEntityFields(s1, temp); copyStackEntityFields(s2, s1); copyStackEntityFields(temp, s2); } public synchronized static StackEntity checkValidUpdateStatusAndUpdateStack(String stackId, String accountId, Template newTemplate, String newTemplateText, UpdateStackType request, int previousStackVersion) throws CloudFormationException { try ( TransactionResource db = Entities.transactionFor( STACK_ENTITY_TRANSACTION_CLASS ) ) { Criteria criteria = Entities.createCriteria(StackEntity.class) .add(Restrictions.eq("stackId", stackId)) .add(Restrictions.eq("accountId", accountId)) .add(Restrictions.eq("recordDeleted", Boolean.FALSE)); List<StackEntity> entityList = criteria.list(); if (entityList == null || entityList.isEmpty()) { throw new ValidationErrorException("Stack does not exist"); } else if (entityList.size() > 1) { throw new InternalFailureException("More than one stack exists with this id " + stackId); } StackEntity currentStackEntity = entityList.get(0); if (currentStackEntity.getStackVersion() != previousStackVersion) { throw new ValidationErrorException("Stack: " + stackId + " is already being updated"); } // Finally make sure the stack state is ok if (currentStackEntity.getStackStatus() != Status.CREATE_COMPLETE && currentStackEntity.getStackStatus() != Status.UPDATE_COMPLETE && currentStackEntity.getStackStatus() != Status.UPDATE_ROLLBACK_COMPLETE) { throw new ValidationErrorException("Stack:" + stackId + " is in " + currentStackEntity.getStackStatus().toString() + " state and can not be updated."); } // put the old stack into the past table PastStackEntity previousStackEntity = new PastStackEntity(); copyStackEntityFields(currentStackEntity, previousStackEntity); addStack(previousStackEntity); // populate all the stack entity fields with the new info StackEntityHelper.populateStackEntityWithTemplate(currentStackEntity, newTemplate); currentStackEntity.setTemplateBody(newTemplateText); currentStackEntity.setStackStatus(Status.UPDATE_IN_PROGRESS); currentStackEntity.setStackStatusReason("User initiated"); currentStackEntity.setLastUpdateOperationTimestamp(new Date()); if (request.getCapabilities() != null && request.getCapabilities().getMember() != null) { currentStackEntity.setCapabilitiesJson(StackEntityHelper.capabilitiesToJson(request.getCapabilities().getMember())); } else { currentStackEntity.setCapabilitiesJson(null); } if (request.getNotificationARNs()!= null && request.getNotificationARNs().getMember() != null) { currentStackEntity.setNotificationARNsJson(StackEntityHelper.notificationARNsToJson(request.getNotificationARNs().getMember())); } else { currentStackEntity.setNotificationARNsJson(null); } if (request.getTags()!= null && request.getTags().getMember() != null) { currentStackEntity.setTagsJson(StackEntityHelper.tagsToJson(request.getTags().getMember())); } currentStackEntity.setStackVersion(previousStackVersion + 1); db.commit( ); return currentStackEntity; } } static final Logger LOG = Logger.getLogger(StackEntityManager.class); // more setters later... public static VersionedStackEntity addStack(StackEntity stackEntity) throws AlreadyExistsException { try ( TransactionResource db = Entities.transactionFor( STACK_ENTITY_TRANSACTION_CLASS ) ) { Criteria criteria = Entities.createCriteria(stackEntity.getClass()) .add(Restrictions.eq("stackName", stackEntity.getStackName())) .add(Restrictions.eq("accountId", stackEntity.getAccountId())) .add(Restrictions.eq("recordDeleted", Boolean.FALSE)); List entityList = criteria.list(); if (!entityList.isEmpty()) { throw new AlreadyExistsException("Stack already exists"); } if (stackEntity.getCreateOperationTimestamp() == null) { stackEntity.setCreateOperationTimestamp(new Date()); } Entities.persist(stackEntity); // do something db.commit( ); } return stackEntity; } public static VersionedStackEntity addStack(PastStackEntity stackEntity) throws AlreadyExistsException { try ( TransactionResource db = Entities.transactionFor( STACK_ENTITY_TRANSACTION_CLASS ) ) { Criteria criteria = Entities.createCriteria(stackEntity.getClass()) .add(Restrictions.eq("stackName", stackEntity.getStackName())) .add(Restrictions.eq("accountId", stackEntity.getAccountId())) .add(Restrictions.eq("stackVersion", stackEntity.getStackVersion())) .add(Restrictions.eq("recordDeleted", Boolean.FALSE)); List entityList = criteria.list(); if (!entityList.isEmpty()) { throw new AlreadyExistsException("Stack already exists"); } if (stackEntity.getCreateOperationTimestamp() == null) { stackEntity.setCreateOperationTimestamp(new Date()); } Entities.persist(stackEntity); // do something db.commit( ); } return stackEntity; } public static List<StackEntity> describeStacks(String accountId, String stackNameOrId) { final List<StackEntity> returnValue; try ( TransactionResource db = Entities.transactionFor( STACK_ENTITY_TRANSACTION_CLASS ) ) { Criteria criteria = Entities.createCriteria(StackEntity.class) .add( accountId != null ? Restrictions.eq("accountId", accountId) : Restrictions.conjunction( ) ); if (stackNameOrId != null) { // stack name or id can be stack name on non-deleted stacks or stack id on any stack criteria.add(Restrictions.or( Restrictions.and(Restrictions.eq("recordDeleted", Boolean.FALSE), Restrictions.eq("stackName", stackNameOrId)), Restrictions.eq("stackId", stackNameOrId)) ); } else { criteria.add(Restrictions.eq("recordDeleted", Boolean.FALSE)); } returnValue = criteria.list(); } return returnValue; } public static List<StackEntity> listStacks(String accountId, List<Status> statusValues) { List<StackEntity> returnValue; try ( TransactionResource db = Entities.transactionFor( STACK_ENTITY_TRANSACTION_CLASS ) ) { Criteria criteria = Entities.createCriteria(StackEntity.class) .add(Restrictions.eq("accountId", accountId)); if (statusValues != null && !statusValues.isEmpty()) { Criterion[] orClauses = new Criterion[statusValues.size()]; int ctr = 0; for (Status statusValue: statusValues) { orClauses[ctr++] = Restrictions.eq("stackStatus", statusValue); } criteria.add(Restrictions.or(orClauses)); } returnValue = criteria.list(); } return returnValue; } public static VersionedStackEntity getNonDeletedVersionedStackById(String stackId, String accountId, int stackVersion) { VersionedStackEntity stackEntity = null; for (Class stackEntityClass: ALL_STACK_ENTITY_CLASSES) { if (stackEntity == null) { try ( TransactionResource db = Entities.transactionFor( STACK_ENTITY_TRANSACTION_CLASS ) ) { Criteria criteria = Entities.createCriteria(stackEntityClass) .add(Restrictions.eq("accountId", accountId)) .add(Restrictions.eq("stackId", stackId)) .add(Restrictions.eq("stackVersion", stackVersion)) .add(Restrictions.eq("recordDeleted", Boolean.FALSE)); List<VersionedStackEntity> entityList = criteria.list(); if (entityList != null && !entityList.isEmpty()) { stackEntity = entityList.get(0); } } } } return stackEntity; } public static StackEntity getNonDeletedStackByNameOrId(String stackNameOrId, String accountId) { StackEntity stackEntity = null; try ( TransactionResource db = Entities.transactionFor( STACK_ENTITY_TRANSACTION_CLASS ) ) { Criteria criteria = Entities.createCriteria(StackEntity.class) .add(accountId != null ? Restrictions.eq("accountId", accountId) : Restrictions.conjunction()) // stack name or id can be stack name on non-deleted stacks or stack id on any stack .add(Restrictions.or(Restrictions.eq("stackName", stackNameOrId), Restrictions.eq("stackId", stackNameOrId))) .add(Restrictions.eq("recordDeleted", Boolean.FALSE)); List<StackEntity> entityList = criteria.list(); if (entityList != null && !entityList.isEmpty()) { stackEntity = entityList.get(0); } } return stackEntity; } public static StackEntity getNonDeletedStackById(String stackId) { StackEntity stackEntity = null; try ( TransactionResource db = Entities.transactionFor( STACK_ENTITY_TRANSACTION_CLASS ) ) { List<StackEntity> entityList = Entities.criteriaQuery(StackEntity.class) .whereEqual(StackEntity_.stackId, stackId) .whereEqual(StackEntity_.recordDeleted, Boolean.FALSE) .list(); if (entityList != null && !entityList.isEmpty()) { stackEntity = entityList.get(0); } } return stackEntity; } public static StackEntity getAnyStackByNameOrId(String stackNameOrId, String accountId) { StackEntity stackEntity = null; try ( TransactionResource db = Entities.transactionFor( STACK_ENTITY_TRANSACTION_CLASS ) ) { Criteria criteria = Entities.createCriteria(StackEntity.class) .add( accountId != null ? Restrictions.eq("accountId", accountId) : Restrictions.conjunction( ) ) // stack name or id can be stack name on non-deleted stacks or stack id on any stack .add(Restrictions.or( Restrictions.and(Restrictions.eq("recordDeleted", Boolean.FALSE), Restrictions.eq("stackName", stackNameOrId)), Restrictions.eq("stackId", stackNameOrId)) ); List<StackEntity> entityList = criteria.list(); if (entityList != null && !entityList.isEmpty()) { stackEntity = entityList.get(0); } } return stackEntity; } public static void deleteStack(String stackId, String accountId) { try (TransactionResource db = Entities.transactionFor(STACK_ENTITY_TRANSACTION_CLASS)) { for (Class stackEntityClass: ALL_STACK_ENTITY_CLASSES) { Criteria criteria = Entities.createCriteria(stackEntityClass) .add(Restrictions.eq("accountId", accountId)) .add(Restrictions.eq("stackId", stackId)) .add(Restrictions.eq("recordDeleted", Boolean.FALSE)); // in this case, all versions List<VersionedStackEntity> entityList = criteria.list(); for (VersionedStackEntity stackEntity : entityList) { stackEntity.setRecordDeleted(Boolean.TRUE); } } db.commit(); } } public static void reallyDeleteAllStackVersionsExcept(String stackId, String accountId, int stackVersion) { try (TransactionResource db = Entities.transactionFor(STACK_ENTITY_TRANSACTION_CLASS)) { for (Class stackEntityClass: ALL_STACK_ENTITY_CLASSES) { Criteria criteria = Entities.createCriteria(stackEntityClass) .add(Restrictions.eq("accountId", accountId)) .add(Restrictions.eq("stackId", stackId)) .add(Restrictions.ne("stackVersion", stackVersion)) .add(Restrictions.eq("recordDeleted", Boolean.FALSE)); List<VersionedStackEntity> entityList = criteria.list(); for (VersionedStackEntity stackEntity : entityList) { Entities.delete(stackEntity); } } db.commit(); } } public static void updateStack(VersionedStackEntity stackEntity) { try ( TransactionResource db = Entities.transactionFor( STACK_ENTITY_TRANSACTION_CLASS ) ) { Criteria criteria = Entities.createCriteria(stackEntity.getClass()) .add(Restrictions.eq("naturalId" , stackEntity.getNaturalId())); VersionedStackEntity dbEntity = (VersionedStackEntity) criteria.uniqueResult(); if (dbEntity == null) { Entities.persist(stackEntity); } else { copyStackEntityFields(stackEntity, dbEntity); } db.commit( ); } } private static void copyStackEntityFields(VersionedStackEntity src, VersionedStackEntity dest) { dest.setCreateOperationTimestamp(src.getCreateOperationTimestamp()); dest.setLastUpdateOperationTimestamp(src.getLastUpdateOperationTimestamp()); dest.setDeleteOperationTimestamp(src.getDeleteOperationTimestamp()); dest.setAccountId(src.getAccountId()); dest.setResourceDependencyManagerJson(src.getResourceDependencyManagerJson()); dest.setCapabilitiesJson(src.getCapabilitiesJson()); dest.setDescription(src.getDescription()); dest.setDisableRollback(src.getDisableRollback()); dest.setPseudoParameterMapJson(src.getPseudoParameterMapJson()); dest.setConditionMapJson(src.getConditionMapJson()); dest.setTemplateBody(src.getTemplateBody()); dest.setMappingJson(src.getMappingJson()); dest.setNotificationARNsJson(src.getNotificationARNsJson()); dest.setWorkingOutputsJson(src.getWorkingOutputsJson()); dest.setOutputsJson(src.getOutputsJson()); dest.setParametersJson(src.getParametersJson()); dest.setStackId(src.getStackId()); dest.setStackName(src.getStackName()); dest.setStackStatus(src.getStackStatus()); dest.setStackStatusReason(src.getStackStatusReason()); dest.setTagsJson(src.getTagsJson()); dest.setTemplateFormatVersion(src.getTemplateFormatVersion()); dest.setTimeoutInMinutes(src.getTimeoutInMinutes()); dest.setRecordDeleted(src.getRecordDeleted()); dest.setStackPolicy(src.getStackPolicy()); dest.setStackVersion(src.getStackVersion()); } }