/*************************************************************************
* Copyright 2009-2015 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.CloudFormationException;
import com.eucalyptus.cloudformation.InternalFailureException;
import com.eucalyptus.cloudformation.ValidationErrorException;
import com.eucalyptus.cloudformation.resources.ResourceInfo;
import com.eucalyptus.cloudformation.resources.ResourceInfoHelper;
import com.eucalyptus.cloudformation.resources.ResourceResolverManager;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionResource;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.hibernate.Criteria;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
/**
* Created by ethomas on 12/19/13.
*/
public class StackResourceEntityManager {
public static void addStackResource(StackResourceEntity stackResourceEntity) {
try ( TransactionResource db =
Entities.transactionFor( stackResourceEntity.getClass() ) ) {
Entities.persist(stackResourceEntity);
db.commit( );
}
}
public static StackResourceEntity updateResourceInfo(StackResourceEntity stackResourceEntity, ResourceInfo resourceInfo) throws CloudFormationException {
stackResourceEntity.setAccountId(resourceInfo.getAccountId());
stackResourceEntity.setResourceType(resourceInfo.getType());
stackResourceEntity.setAllowedByCondition(resourceInfo.getAllowedByCondition());
stackResourceEntity.setCreatedEnoughToDelete(resourceInfo.getCreatedEnoughToDelete());
stackResourceEntity.setDeletionPolicy(resourceInfo.getDeletionPolicy());
stackResourceEntity.setDescription(resourceInfo.getDescription());
stackResourceEntity.setLogicalResourceId(resourceInfo.getLogicalResourceId());
stackResourceEntity.setMetadataJson(resourceInfo.getMetadataJson());
stackResourceEntity.setPhysicalResourceId(resourceInfo.getPhysicalResourceId());
stackResourceEntity.setPropertiesJson(resourceInfo.getPropertiesJson());
stackResourceEntity.setReady(resourceInfo.getReady());
stackResourceEntity.setReferenceValueJson(resourceInfo.getReferenceValueJson());
stackResourceEntity.setUpdatePolicyJson(resourceInfo.getUpdatePolicyJson());
stackResourceEntity.setCreationPolicyJson(resourceInfo.getCreationPolicyJson());
stackResourceEntity.setResourceAttributesJson(ResourceInfoHelper.getResourceAttributesJson(resourceInfo));
return stackResourceEntity;
}
public static void copyStackResourceEntityData(StackResourceEntity sourceEntity, StackResourceEntity destEntity) {
destEntity.setRecordDeleted(sourceEntity.getRecordDeleted());
destEntity.setCreatedEnoughToDelete(sourceEntity.getCreatedEnoughToDelete());
destEntity.setUpdateType(sourceEntity.getUpdateType());
destEntity.setDescription(sourceEntity.getDescription());
destEntity.setLogicalResourceId(sourceEntity.getLogicalResourceId());
destEntity.setPhysicalResourceId(sourceEntity.getPhysicalResourceId());
destEntity.setResourceStatus(sourceEntity.getResourceStatus());
destEntity.setResourceStatusReason(sourceEntity.getResourceStatusReason());
destEntity.setResourceType(sourceEntity.getResourceType());
destEntity.setStackId(sourceEntity.getStackId());
destEntity.setStackName(sourceEntity.getStackName());
destEntity.setAccountId(sourceEntity.getAccountId());
destEntity.setMetadataJson(sourceEntity.getMetadataJson());
destEntity.setReady(sourceEntity.getReady());
destEntity.setPropertiesJson(sourceEntity.getPropertiesJson());
destEntity.setUpdatePolicyJson(sourceEntity.getUpdatePolicyJson());
destEntity.setCreationPolicyJson(sourceEntity.getCreationPolicyJson());
destEntity.setDeletionPolicy(sourceEntity.getDeletionPolicy());
destEntity.setAllowedByCondition(sourceEntity.getAllowedByCondition());
destEntity.setReferenceValueJson(sourceEntity.getReferenceValueJson());
destEntity.setResourceAttributesJson(sourceEntity.getResourceAttributesJson());
destEntity.setResourceVersion(sourceEntity.getResourceVersion());
}
public static void updateStackResource(StackResourceEntity stackResourceEntity) {
try ( TransactionResource db =
Entities.transactionFor( stackResourceEntity.getClass() ) ) {
Criteria criteria = Entities.createCriteria(stackResourceEntity.getClass())
.add(Restrictions.eq("naturalId" , stackResourceEntity.getNaturalId()));
StackResourceEntity dbEntity = (StackResourceEntity) criteria.uniqueResult();
if (dbEntity == null) {
Entities.persist(stackResourceEntity);
} else {
copyStackResourceEntityData(stackResourceEntity, dbEntity);
}
db.commit( );
}
}
public static StackResourceEntity getStackResource(String stackId, String accountId, String logicalResourceId, int resourceVersion) {
StackResourceEntity stackResourceEntity = null;
try ( TransactionResource db =
Entities.transactionFor( StackResourceEntity.class ) ) {
Criteria criteria = Entities.createCriteria(StackResourceEntity.class)
.add(Restrictions.eq("accountId" , accountId))
.add(Restrictions.eq("stackId" , stackId))
.add(Restrictions.eq("logicalResourceId" , logicalResourceId))
.add(Restrictions.eq("resourceVersion", resourceVersion))
.add(Restrictions.eq("recordDeleted", Boolean.FALSE));
List<StackResourceEntity> stackResourceEntityList = criteria.list();
if (stackResourceEntityList != null && !stackResourceEntityList.isEmpty()) {
stackResourceEntity = stackResourceEntityList.get(0);
}
}
return stackResourceEntity;
}
public static StackResourceEntity getStackResourceByPhysicalResourceId(String stackId, String accountId, String physicalResourceId, int resourceVersion) {
StackResourceEntity stackResourceEntity = null;
try ( TransactionResource db =
Entities.transactionFor( StackResourceEntity.class ) ) {
Criteria criteria = Entities.createCriteria(StackResourceEntity.class)
.add(Restrictions.eq("accountId" , accountId))
.add(Restrictions.eq("stackId" , stackId))
.add(Restrictions.eq("physicalResourceId" , physicalResourceId))
.add(Restrictions.eq("resourceVersion", resourceVersion))
.add(Restrictions.eq("recordDeleted", Boolean.FALSE));
List<StackResourceEntity> stackResourceEntityList = criteria.list();
if (stackResourceEntityList != null && !stackResourceEntityList.isEmpty()) {
stackResourceEntity = stackResourceEntityList.get(0);
}
db.commit( );
}
return stackResourceEntity;
}
public static List<StackResourceEntity> getStackResources(String stackId, String accountId, int resourceVersion) {
List<StackResourceEntity> stackResourceEntityList = Lists.newArrayList();
try ( TransactionResource db =
Entities.transactionFor( StackResourceEntity.class ) ) {
Criteria criteria = Entities.createCriteria(StackResourceEntity.class)
.add(Restrictions.eq( "accountId" , accountId))
.add(Restrictions.eq( "stackId" , stackId))
.add(Restrictions.eq("resourceVersion", resourceVersion))
.add(Restrictions.eq("recordDeleted", Boolean.FALSE));
stackResourceEntityList = criteria.list();
}
return stackResourceEntityList;
}
public static void deleteStackResources(String stackId, String accountId) {
try ( TransactionResource db =
Entities.transactionFor( StackResourceEntity.class ) ) {
Criteria criteria = Entities.createCriteria(StackResourceEntity.class)
.add(Restrictions.eq("accountId", accountId))
.add(Restrictions.eq("stackId", stackId))
.add(Restrictions.eq("recordDeleted", Boolean.FALSE));
for (StackResourceEntity stackResourceEntity : (List<StackResourceEntity>) criteria.list()) {
stackResourceEntity.setRecordDeleted(Boolean.TRUE);
}
db.commit( );
}
}
public static void reallyDeleteAllVersionsExcept(String stackId, String accountId, int resourceVersion) {
try ( TransactionResource db =
Entities.transactionFor( StackResourceEntity.class ) ) {
Criteria criteria = Entities.createCriteria(StackResourceEntity.class)
.add(Restrictions.eq("accountId", accountId))
.add(Restrictions.eq("stackId", stackId))
.add(Restrictions.ne("resourceVersion", resourceVersion))
.add(Restrictions.eq("recordDeleted", Boolean.FALSE));
for (StackResourceEntity stackResourceEntity : (List<StackResourceEntity>) criteria.list()) {
Entities.delete(stackResourceEntity);
}
db.commit( );
}
}
public static ResourceInfo getResourceInfo(String stackId, String accountId, String logicalResourceId, int resourceVersion)
throws CloudFormationException {
return getResourceInfo(getStackResource(stackId, accountId, logicalResourceId, resourceVersion));
}
public static ResourceInfo getResourceInfo(StackResourceEntity stackResourceEntity)
throws CloudFormationException {
if (stackResourceEntity == null) return null;
ResourceInfo resourceInfo = new ResourceResolverManager().resolveResourceInfo(stackResourceEntity.getResourceType());
resourceInfo.setAccountId(stackResourceEntity.getAccountId());
resourceInfo.setAllowedByCondition(stackResourceEntity.getAllowedByCondition());
resourceInfo.setCreatedEnoughToDelete(stackResourceEntity.getCreatedEnoughToDelete());
resourceInfo.setDescription(stackResourceEntity.getDescription());
resourceInfo.setDeletionPolicy(stackResourceEntity.getDeletionPolicy());
resourceInfo.setLogicalResourceId(stackResourceEntity.getLogicalResourceId());
resourceInfo.setMetadataJson(stackResourceEntity.getMetadataJson());
resourceInfo.setPhysicalResourceId(stackResourceEntity.getPhysicalResourceId());
resourceInfo.setPropertiesJson(stackResourceEntity.getPropertiesJson());
resourceInfo.setReady(stackResourceEntity.getReady());
resourceInfo.setReferenceValueJson(stackResourceEntity.getReferenceValueJson());
resourceInfo.setUpdatePolicyJson(stackResourceEntity.getUpdatePolicyJson());
resourceInfo.setCreationPolicyJson(stackResourceEntity.getCreationPolicyJson());
ResourceInfoHelper.setResourceAttributesJson(resourceInfo, stackResourceEntity.getResourceAttributesJson());
return resourceInfo;
}
public static StackResourceEntity describeStackResource(String accountId, String stackNameOrId, String logicalResourceId) throws CloudFormationException {
StackResourceEntity matchingStackResourceEntity = null;
String stackId = null;
try ( TransactionResource db =
Entities.transactionFor( StackResourceEntity.class ) ) {
// There is some weirdness in this request. The stack name represents either the stack name of the
// non-deleted stack or the stack id of the deleted or non-deleted stack.
Criteria criteria = Entities.createCriteria(StackResourceEntity.class)
.add(accountId!=null ? Restrictions.eq("accountId", accountId) : Restrictions.conjunction( ))
.add(Restrictions.or(
Restrictions.and(Restrictions.eq("recordDeleted", Boolean.FALSE), Restrictions.eq("stackName", stackNameOrId)),
Restrictions.eq("stackId", stackNameOrId))
)
.add(Restrictions.ne("resourceStatus", Status.NOT_STARTED)); // placeholder, AWS doesn't return these
List<StackResourceEntity> result = takeLatestVersions(criteria.list());
if (result == null || result.isEmpty()) {
// TODO: in theory the stack may exist but with no resources. Either way though there is an error, so this is ok.
throw new ValidationErrorException("Stack with name " + stackNameOrId +" does not exist");
}
for (StackResourceEntity stackResourceEntity: result) {
if (stackId == null) {
stackId = stackResourceEntity.getStackId();
} else if (!stackId.equals(stackResourceEntity.getStackId())) {
throw new InternalFailureException("Got results from more than one stack");
}
if (logicalResourceId.equals(stackResourceEntity.getLogicalResourceId())) {
if (matchingStackResourceEntity != null) {
throw new InternalFailureException("More than one record exists for Resource " + logicalResourceId + " on stack " + stackId);
} else {
matchingStackResourceEntity = stackResourceEntity;
}
}
}
if (matchingStackResourceEntity == null) {
throw new ValidationErrorException("Resource " + logicalResourceId + " does not exist for stack " + stackId);
}
}
return matchingStackResourceEntity;
}
public static List<StackResourceEntity> describeStackResources( String accountId, String stackNameOrId ) {
return describeStackResources(accountId, stackNameOrId, null, null);
}
public static List<StackResourceEntity> describeStackResources(
@Nullable final String accountId,
@Nullable final String stackNameOrId,
@Nullable final String physicalResourceId,
@Nullable final String logicalResourceId
) {
if ( stackNameOrId == null && physicalResourceId == null ) {
throw new IllegalArgumentException( "stackNameOrId or physicalResourceId required" );
}
try ( final TransactionResource db = Entities.transactionFor( StackResourceEntity.class ) ) {
// There is some weirdness in this request. The stack name represents either the stack name of the
// non-deleted stack or the stack id of the deleted or non-deleted stack.
final Criteria criteria = Entities.createCriteria( StackResourceEntity.class ).add( accountId != null ?
Restrictions.eq("accountId", accountId) :
Restrictions.conjunction( ) );
if ( stackNameOrId != null ) { // stack explicitly specified
criteria.add(Restrictions.or(
Restrictions.and( Restrictions.eq( "recordDeleted", Boolean.FALSE ), Restrictions.eq( "stackName", stackNameOrId ) ),
Restrictions.eq( "stackId", stackNameOrId ) )
);
}
if ( physicalResourceId != null ) { // stack specified via physical resource identifier
criteria.add( Subqueries.propertyIn(
"stackId",
DetachedCriteria.forClass( StackResourceEntity.class, "subres" )
.add( Restrictions.eq( "subres.physicalResourceId", physicalResourceId ) )
.add( Restrictions.eq( "subres.recordDeleted", Boolean.FALSE ) )
.setProjection( Projections.property( "subres.stackId" ) )
) );
}
if ( logicalResourceId != null ) { // filter results for a specific logical resource
criteria.add( Restrictions.eq( "logicalResourceId", logicalResourceId ) );
}
criteria.add(Restrictions.ne("resourceStatus", Status.NOT_STARTED)); // placeholder, AWS doesn't return these
//noinspection unchecked
return takeLatestVersions(criteria.list());
}
}
public static List<StackResourceEntity> listStackResources(String accountId, String stackNameOrId) {
return describeStackResources(accountId, stackNameOrId);
}
private static List<StackResourceEntity> takeLatestVersions(List<StackResourceEntity> original) {
if (original == null) return null;
Map<String, StackResourceEntity> latestVersionMap = Maps.newLinkedHashMap();
for (StackResourceEntity stackResourceEntity : original) {
String key = stackResourceEntity.getAccountId() + " | " + stackResourceEntity.getStackId() + " | " + stackResourceEntity.getLogicalResourceId();
if (!latestVersionMap.containsKey(key)) {
latestVersionMap.put(key, stackResourceEntity);
} else {
StackResourceEntity alreadyInMapStackResourceEntity = latestVersionMap.get(key);
if (alreadyInMapStackResourceEntity.getResourceVersion() < stackResourceEntity.getResourceVersion()) {
latestVersionMap.put(key, stackResourceEntity);
}
}
}
return Lists.newArrayList(latestVersionMap.values());
}
public static void flattenResources(String stackId, String accountId, int finalResourceVersion) {
// This method is used for delete. It essentially takes all the items you would get from describeResources() and makes them the only version that is seen.
try ( TransactionResource db =
Entities.transactionFor( StackResourceEntity.class ) ) {
Criteria criteria = Entities.createCriteria(StackResourceEntity.class)
.add(Restrictions.eq("accountId" , accountId))
.add(Restrictions.eq("stackId" , stackId))
.add(Restrictions.eq("recordDeleted", Boolean.FALSE));
List<StackResourceEntity> stackResourceEntityList = criteria.list();
Map<String, List<StackResourceEntity>> stackResourceEntityVersionMap = Maps.newHashMap();
if (stackResourceEntityList != null) {
for (StackResourceEntity stackResourceEntity : stackResourceEntityList) {
String key = stackResourceEntity.getAccountId() + " | " + stackResourceEntity.getStackId() + " | " + stackResourceEntity.getLogicalResourceId();
if (!stackResourceEntityVersionMap.containsKey(key)) {
stackResourceEntityVersionMap.put(key, Lists.<StackResourceEntity>newArrayList());
}
stackResourceEntityVersionMap.get(key).add(stackResourceEntity);
}
for (List<StackResourceEntity> stackResourceEntityPerKeyList : stackResourceEntityVersionMap.values()) {
StackResourceEntity maxEntity = null;
for (StackResourceEntity stackResourceEntity : stackResourceEntityPerKeyList) {
if (maxEntity == null) {
maxEntity = stackResourceEntity;
} else if (stackResourceEntity.getResourceStatus() != Status.NOT_STARTED && maxEntity.getResourceStatus() == Status.NOT_STARTED) {
maxEntity = stackResourceEntity;
} else if (stackResourceEntity.getResourceStatus() == Status.NOT_STARTED && maxEntity.getResourceStatus() == Status.NOT_STARTED
&& stackResourceEntity.getResourceVersion() > maxEntity.getResourceVersion()) {
maxEntity = stackResourceEntity;
} else if (stackResourceEntity.getResourceStatus() != Status.NOT_STARTED && maxEntity.getResourceStatus() != Status.NOT_STARTED
&& stackResourceEntity.getResourceVersion() > maxEntity.getResourceVersion()) {
maxEntity = stackResourceEntity;
}
}
maxEntity.setResourceVersion(finalResourceVersion);
for (StackResourceEntity stackResourceEntity : stackResourceEntityPerKeyList) {
if (stackResourceEntity != maxEntity) { // this is a reference equals on purpose
Entities.delete(stackResourceEntity);
}
}
}
}
db.commit();
}
}
public static String findOuterStackArnIfExists(String stackId, String accountId) throws ValidationErrorException {
String returnValue = null;
try ( TransactionResource db =
Entities.transactionFor( StackResourceEntity.class ) ) {
Criteria criteria = Entities.createCriteria(StackResourceEntity.class)
.add(Restrictions.eq("accountId", accountId))
.add(Restrictions.eq("physicalResourceId", stackId)) // inner stack ARN == physical resource id
.add(Restrictions.eq("recordDeleted", Boolean.FALSE));
List<StackResourceEntity> stackResourceEntityList = criteria.list();
if (stackResourceEntityList != null) {
for (StackResourceEntity stackResourceEntity : stackResourceEntityList) {
if (returnValue == null) {
returnValue = stackResourceEntity.getStackId();
}
if (!returnValue.equals(stackResourceEntity.getStackId())) {
throw new ValidationErrorException("Stack " + stackId + " is a resource in more than one stack:" + returnValue + " and " + stackResourceEntity.getStackId());
}
}
}
return returnValue;
}
}
}