/*************************************************************************
* 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.resources.standard.actions;
import com.eucalyptus.auth.Accounts;
import com.eucalyptus.cloudformation.ValidationErrorException;
import com.eucalyptus.cloudformation.resources.EC2Helper;
import com.eucalyptus.cloudformation.resources.ResourceAction;
import com.eucalyptus.cloudformation.resources.ResourceInfo;
import com.eucalyptus.cloudformation.resources.ResourceProperties;
import com.eucalyptus.cloudformation.resources.standard.TagHelper;
import com.eucalyptus.cloudformation.resources.standard.info.AWSEC2VolumeResourceInfo;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSEC2VolumeProperties;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.EC2Tag;
import com.eucalyptus.cloudformation.template.JsonHelper;
import com.eucalyptus.cloudformation.util.MessageHelper;
import com.eucalyptus.cloudformation.workflow.ResourceFailureException;
import com.eucalyptus.cloudformation.workflow.RetryAfterConditionCheckFailedException;
import com.eucalyptus.cloudformation.workflow.steps.Step;
import com.eucalyptus.cloudformation.workflow.steps.StepBasedResourceAction;
import com.eucalyptus.cloudformation.workflow.steps.UpdateStep;
import com.eucalyptus.cloudformation.workflow.updateinfo.UpdateType;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.Topology;
import com.eucalyptus.compute.common.Compute;
import com.eucalyptus.compute.common.CreateSnapshotResponseType;
import com.eucalyptus.compute.common.CreateSnapshotType;
import com.eucalyptus.compute.common.CreateTagsResponseType;
import com.eucalyptus.compute.common.CreateTagsType;
import com.eucalyptus.compute.common.CreateVolumeResponseType;
import com.eucalyptus.compute.common.CreateVolumeType;
import com.eucalyptus.compute.common.DeleteTagsResponseType;
import com.eucalyptus.compute.common.DeleteTagsType;
import com.eucalyptus.compute.common.DeleteVolumeResponseType;
import com.eucalyptus.compute.common.DeleteVolumeType;
import com.eucalyptus.compute.common.DescribeSnapshotsResponseType;
import com.eucalyptus.compute.common.DescribeSnapshotsType;
import com.eucalyptus.compute.common.DescribeTagsResponseType;
import com.eucalyptus.compute.common.DescribeTagsType;
import com.eucalyptus.compute.common.DescribeVolumeAttributeResponseType;
import com.eucalyptus.compute.common.DescribeVolumeAttributeType;
import com.eucalyptus.compute.common.DescribeVolumesResponseType;
import com.eucalyptus.compute.common.DescribeVolumesType;
import com.eucalyptus.compute.common.Filter;
import com.eucalyptus.compute.common.ModifyVolumeAttributeType;
import com.eucalyptus.compute.common.TagInfo;
import com.eucalyptus.configurable.ConfigurableClass;
import com.eucalyptus.configurable.ConfigurableField;
import com.eucalyptus.util.async.AsyncRequests;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import static com.eucalyptus.util.async.AsyncExceptions.asWebServiceErrorMessage;
/**
* Created by ethomas on 2/3/14.
*/
@ConfigurableClass( root = "cloudformation", description = "Parameters controlling cloud formation")
public class AWSEC2VolumeResourceAction extends StepBasedResourceAction {
private AWSEC2VolumeProperties properties = new AWSEC2VolumeProperties();
private AWSEC2VolumeResourceInfo info = new AWSEC2VolumeResourceInfo();
@ConfigurableField(initial = "300", description = "The amount of time (in seconds) to wait for a volume to be available after create)")
public static volatile Integer VOLUME_AVAILABLE_MAX_CREATE_RETRY_SECS = 300;
@ConfigurableField(initial = "300", description = "The amount of time (in seconds) to wait for a snapshot to be complete (if specified as the deletion policy) before a volume is deleted)")
public static volatile Integer VOLUME_SNAPSHOT_COMPLETE_MAX_DELETE_RETRY_SECS = 300;
@ConfigurableField(initial = "300", description = "The amount of time (in seconds) to wait for a volume to be deleted)")
public static volatile Integer VOLUME_DELETED_MAX_DELETE_RETRY_SECS = 300;
public AWSEC2VolumeResourceAction() {
super(fromEnum(CreateSteps.class), fromEnum(DeleteSteps.class), fromUpdateEnum(UpdateNoInterruptionSteps.class), null);
}
@Override
public UpdateType getUpdateType(ResourceAction resourceAction, boolean stackTagsChanged) {
UpdateType updateType = info.supportsTags() && stackTagsChanged ? UpdateType.NO_INTERRUPTION : UpdateType.NONE;
AWSEC2VolumeResourceAction otherAction = (AWSEC2VolumeResourceAction) resourceAction;
if (!Objects.equals(properties.getAutoEnableIO(), otherAction.properties.getAutoEnableIO())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getAvailabilityZone(), otherAction.properties.getAvailabilityZone())) {
updateType = UpdateType.max(updateType, UpdateType.UNSUPPORTED);
}
if (!Objects.equals(properties.getEncrypted(), otherAction.properties.getEncrypted())) {
updateType = UpdateType.max(updateType, UpdateType.UNSUPPORTED);
}
if (!Objects.equals(properties.getIops(), otherAction.properties.getIops())) {
updateType = UpdateType.max(updateType, UpdateType.UNSUPPORTED);
}
if (!Objects.equals(properties.getKmsKeyId(), otherAction.properties.getKmsKeyId())) {
updateType = UpdateType.max(updateType, UpdateType.UNSUPPORTED);
}
if (!Objects.equals(properties.getSize(), otherAction.properties.getSize())) {
updateType = UpdateType.max(updateType, UpdateType.UNSUPPORTED);
}
if (!Objects.equals(properties.getSnapshotId(), otherAction.properties.getSnapshotId())) {
updateType = UpdateType.max(updateType, UpdateType.UNSUPPORTED);
}
if (!Objects.equals(properties.getTags(), otherAction.properties.getTags())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION); // docs are wrong here, experimentation shows no interruption at AWS
}
if (!Objects.equals(properties.getVolumeType(), otherAction.properties.getVolumeType())) {
updateType = UpdateType.max(updateType, UpdateType.UNSUPPORTED);
}
return updateType;
}
private enum CreateSteps implements Step {
CREATE_VOLUME {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VolumeResourceAction action = (AWSEC2VolumeResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
CreateVolumeType createVolumeType = MessageHelper.createMessage(CreateVolumeType.class, action.info.getEffectiveUserId());
createVolumeType.setAvailabilityZone(action.properties.getAvailabilityZone());
if (action.properties.getEncrypted() != null) {
createVolumeType.setEncrypted(action.properties.getEncrypted());
}
// KmsKeyId not supported
if (action.properties.getIops() != null) {
createVolumeType.setIops(action.properties.getIops());
}
if (action.properties.getSize() != null) {
createVolumeType.setSize(action.properties.getSize());
}
if (action.properties.getSnapshotId() != null) {
createVolumeType.setSnapshotId(action.properties.getSnapshotId());
}
if (action.properties.getVolumeType() != null) {
createVolumeType.setVolumeType(action.properties.getVolumeType());
} else {
createVolumeType.setVolumeType("standard");
}
CreateVolumeResponseType createVolumeResponseType = AsyncRequests.<CreateVolumeType,CreateVolumeResponseType> sendSync(configuration, createVolumeType);
action.info.setPhysicalResourceId(createVolumeResponseType.getVolume().getVolumeId());
action.info.setCreatedEnoughToDelete(true);
action.info.setReferenceValueJson(JsonHelper.getStringFromJsonNode(new TextNode(action.info.getPhysicalResourceId())));
return action;
}
},
SET_ATTRIBUTES {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VolumeResourceAction action = (AWSEC2VolumeResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (action.properties.getAutoEnableIO() != null) {
ModifyVolumeAttributeType modifyVolumeAttributeTypeType = MessageHelper.createMessage(ModifyVolumeAttributeType.class, action.info.getEffectiveUserId());
modifyVolumeAttributeTypeType.setVolumeId(action.info.getPhysicalResourceId());
modifyVolumeAttributeTypeType.setAutoEnableIO(action.properties.getAutoEnableIO());
AsyncRequests.sendSync(configuration, modifyVolumeAttributeTypeType);
}
return action;
}
},
VERIFY_AVAILABLE {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VolumeResourceAction action = (AWSEC2VolumeResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
DescribeVolumesType describeVolumesType = MessageHelper.createMessage(DescribeVolumesType.class, action.info.getEffectiveUserId());
describeVolumesType.getFilterSet( ).add( Filter.filter( "volume-id", action.info.getPhysicalResourceId( ) ) );
DescribeVolumesResponseType describeVolumesResponseType;
try {
describeVolumesResponseType = AsyncRequests.sendSync( configuration, describeVolumesType);
} catch ( final Exception e ) {
throw new ValidationErrorException("Error describing volume " + action.info.getPhysicalResourceId() + ":" + asWebServiceErrorMessage( e, e.getMessage() ) );
}
if (describeVolumesResponseType.getVolumeSet().size()==0) {
throw new RetryAfterConditionCheckFailedException("Volume " + action.info.getPhysicalResourceId() + " not yet available");
}
if (!"available".equals(describeVolumesResponseType.getVolumeSet().get(0).getStatus())) {
throw new RetryAfterConditionCheckFailedException("Volume " + action.info.getPhysicalResourceId() + " not yet available");
}
return action;
}
@Override
public Integer getTimeout() {
return VOLUME_AVAILABLE_MAX_CREATE_RETRY_SECS;
}
},
CREATE_TAGS {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VolumeResourceAction action = (AWSEC2VolumeResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
// Create 'system' tags as admin user
String effectiveAdminUserId = Accounts.lookupPrincipalByAccountNumber( Accounts.lookupPrincipalByUserId(action.info.getEffectiveUserId()).getAccountNumber( ) ).getUserId();
CreateTagsType createSystemTagsType = MessageHelper.createPrivilegedMessage(CreateTagsType.class, effectiveAdminUserId);
createSystemTagsType.setResourcesSet(Lists.newArrayList(action.info.getPhysicalResourceId()));
createSystemTagsType.setTagSet(EC2Helper.createTagSet(TagHelper.getEC2SystemTags(action.info, action.getStackEntity())));
AsyncRequests.<CreateTagsType, CreateTagsResponseType>sendSync(configuration, createSystemTagsType);
// Create non-system tags as regular user
List<EC2Tag> tags = TagHelper.getEC2StackTags(action.getStackEntity());
if (action.properties.getTags() != null && !action.properties.getTags().isEmpty()) {
TagHelper.checkReservedEC2TemplateTags(action.properties.getTags());
tags.addAll(action.properties.getTags());
}
if (!tags.isEmpty()) {
CreateTagsType createTagsType = MessageHelper.createMessage(CreateTagsType.class, action.info.getEffectiveUserId());
createTagsType.setResourcesSet(Lists.newArrayList(action.info.getPhysicalResourceId()));
createTagsType.setTagSet(EC2Helper.createTagSet(tags));
AsyncRequests.<CreateTagsType, CreateTagsResponseType>sendSync(configuration, createTagsType);
}
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private enum DeleteSteps implements Step {
CREATE_SNAPSHOT {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VolumeResourceAction action = (AWSEC2VolumeResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (volumeDeleted(action, configuration)) return action;
if (!("Snapshot".equals(action.info.getDeletionPolicy()))) return action;
CreateSnapshotType createSnapshotType = MessageHelper.createMessage(CreateSnapshotType.class, action.info.getEffectiveUserId());
createSnapshotType.setVolumeId(action.info.getPhysicalResourceId());
CreateSnapshotResponseType createSnapshotResponseType = AsyncRequests.<CreateSnapshotType, CreateSnapshotResponseType>sendSync(configuration, createSnapshotType);
if (createSnapshotResponseType.getSnapshot() == null || createSnapshotResponseType.getSnapshot().getSnapshotId() == null) {
throw new ResourceFailureException("Unable to create snapshot on delete for volume " + action.info.getPhysicalResourceId());
} else {
action.info.setSnapshotIdForDelete(JsonHelper.getStringFromJsonNode(new TextNode(createSnapshotResponseType.getSnapshot().getSnapshotId())));
}
return action;
}
},
VERIFY_SNAPSHOT_COMPLETE {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VolumeResourceAction action = (AWSEC2VolumeResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (volumeDeleted(action, configuration)) return action;
if (!("Snapshot".equals(action.info.getDeletionPolicy()))) return action;
DescribeSnapshotsType describeSnapshotsType = MessageHelper.createMessage(DescribeSnapshotsType.class, action.info.getEffectiveUserId());
String snapshotId = JsonHelper.getJsonNodeFromString(action.info.getSnapshotIdForDelete()).asText();
describeSnapshotsType.getFilterSet( ).add( Filter.filter( "snapshot-id", snapshotId ) );
DescribeSnapshotsResponseType describeSnapshotsResponseType = AsyncRequests.sendSync(configuration, describeSnapshotsType);
if (describeSnapshotsResponseType.getSnapshotSet() == null || describeSnapshotsResponseType.getSnapshotSet().isEmpty()) {
throw new RetryAfterConditionCheckFailedException("Snapshot " + snapshotId + " not yet complete");
}
if ("error".equals(describeSnapshotsResponseType.getSnapshotSet().get(0).getStatus())) {
throw new ResourceFailureException("Error creating snapshot " + snapshotId + ", while deleting volume " + action.info.getPhysicalResourceId());
} else if (!"completed".equals(describeSnapshotsResponseType.getSnapshotSet().get(0).getStatus())) {
throw new RetryAfterConditionCheckFailedException("Snapshot " + snapshotId + " not yet complete");
}
return action;
}
@Override
public Integer getTimeout() {
return VOLUME_SNAPSHOT_COMPLETE_MAX_DELETE_RETRY_SECS;
}
},
CREATE_SNAPSHOT_TAGS {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VolumeResourceAction action = (AWSEC2VolumeResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (volumeDeleted(action, configuration)) return action;
if (!("Snapshot".equals(action.info.getDeletionPolicy()))) return action;
String snapshotId = JsonHelper.getJsonNodeFromString(action.info.getSnapshotIdForDelete()).asText();
// Create 'system' tags as admin user
String effectiveAdminUserId = Accounts.lookupPrincipalByAccountNumber( Accounts.lookupPrincipalByUserId(action.info.getEffectiveUserId()).getAccountNumber( ) ).getUserId();
CreateTagsType createSystemTagsType = MessageHelper.createPrivilegedMessage(CreateTagsType.class, effectiveAdminUserId);
createSystemTagsType.setResourcesSet(Lists.newArrayList(snapshotId));
createSystemTagsType.setTagSet(EC2Helper.createTagSet(TagHelper.getEC2SystemTags(action.info, action.getStackEntity())));
AsyncRequests.<CreateTagsType, CreateTagsResponseType>sendSync(configuration, createSystemTagsType);
// Create non-system tags as regular user
List<EC2Tag> tags = TagHelper.getEC2StackTags(action.getStackEntity());
if (action.properties.getTags() != null && !action.properties.getTags().isEmpty()) {
TagHelper.checkReservedEC2TemplateTags(action.properties.getTags());
tags.addAll(action.properties.getTags());
}
if (!tags.isEmpty()) {
CreateTagsType createTagsType = MessageHelper.createMessage(CreateTagsType.class, action.info.getEffectiveUserId());
createTagsType.setResourcesSet(Lists.newArrayList(snapshotId));
createTagsType.setTagSet(EC2Helper.createTagSet(tags));
AsyncRequests.<CreateTagsType, CreateTagsResponseType>sendSync(configuration, createTagsType);
}
return action;
}
},
DELETE_VOLUME {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VolumeResourceAction action = (AWSEC2VolumeResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (volumeDeleted(action, configuration)) return action;
DeleteVolumeType deleteVolumeType = MessageHelper.createMessage(DeleteVolumeType.class, action.info.getEffectiveUserId());
deleteVolumeType.setVolumeId(action.info.getPhysicalResourceId());
AsyncRequests.<DeleteVolumeType, DeleteVolumeResponseType>sendSync(configuration, deleteVolumeType);
return action;
}
},
VERIFY_DELETE {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VolumeResourceAction action = (AWSEC2VolumeResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (volumeDeleted(action, configuration)) return action;
throw new RetryAfterConditionCheckFailedException("Volume " + action.info.getPhysicalResourceId() + " not yet deleted");
}
@Override
public Integer getTimeout() {
return VOLUME_DELETED_MAX_DELETE_RETRY_SECS;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
private static boolean volumeDeleted(AWSEC2VolumeResourceAction action, ServiceConfiguration configuration) throws Exception {
if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return true;
DescribeVolumesType describeVolumesType = MessageHelper.createMessage(DescribeVolumesType.class, action.info.getEffectiveUserId());
describeVolumesType.getFilterSet( ).add( Filter.filter( "volume-id", action.info.getPhysicalResourceId( ) ) );
DescribeVolumesResponseType describeVolumesResponseType;
try {
describeVolumesResponseType = AsyncRequests.sendSync(configuration, describeVolumesType);
} catch ( final Exception e ) {
throw new ValidationErrorException("Error describing volume " + action.info.getPhysicalResourceId() + ":" + asWebServiceErrorMessage( e, e.getMessage() ) );
}
if (describeVolumesResponseType.getVolumeSet().size() == 0) {
return true; // already deleted
}
if ("deleted".equals(describeVolumesResponseType.getVolumeSet().get(0).getStatus())) {
return true;
}
return false;
}
}
private enum UpdateNoInterruptionSteps implements UpdateStep {
UPDATE_ATTRIBUTES {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2VolumeResourceAction oldAction = (AWSEC2VolumeResourceAction) oldResourceAction;
AWSEC2VolumeResourceAction newAction = (AWSEC2VolumeResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
DescribeVolumeAttributeType describeVolumeAttributeType = MessageHelper.createMessage(DescribeVolumeAttributeType.class, newAction.info.getEffectiveUserId());
describeVolumeAttributeType.setVolumeId(newAction.info.getPhysicalResourceId());
describeVolumeAttributeType.setAttribute("autoEnableIO");
DescribeVolumeAttributeResponseType describeVolumeAttributeResponseType = AsyncRequests.sendSync(configuration, describeVolumeAttributeType);
Boolean originalValue = null;
if (describeVolumeAttributeResponseType != null) {
originalValue = describeVolumeAttributeResponseType.getAutoEnableIO();
}
if (!Objects.equals(originalValue, newAction.properties.getAutoEnableIO())) {
ModifyVolumeAttributeType modifyVolumeAttributeTypeType = MessageHelper.createMessage(ModifyVolumeAttributeType.class, newAction.info.getEffectiveUserId());
modifyVolumeAttributeTypeType.setVolumeId(newAction.info.getPhysicalResourceId());
modifyVolumeAttributeTypeType.setAutoEnableIO(newAction.properties.getAutoEnableIO());
AsyncRequests.sendSync(configuration, modifyVolumeAttributeTypeType);
}
return newAction;
}
},
UPDATE_TAGS {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2VolumeResourceAction oldAction = (AWSEC2VolumeResourceAction) oldResourceAction;
AWSEC2VolumeResourceAction newAction = (AWSEC2VolumeResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
DescribeTagsType describeTagsType = MessageHelper.createMessage(DescribeTagsType.class, newAction.info.getEffectiveUserId());
describeTagsType.setFilterSet(Lists.newArrayList(Filter.filter("resource-id", newAction.info.getPhysicalResourceId())));
DescribeTagsResponseType describeTagsResponseType = AsyncRequests.sendSync(configuration, describeTagsType);
Set<EC2Tag> existingTags = Sets.newLinkedHashSet();
if (describeTagsResponseType != null && describeTagsResponseType.getTagSet() != null) {
for (TagInfo tagInfo: describeTagsResponseType.getTagSet()) {
EC2Tag tag = new EC2Tag();
tag.setKey(tagInfo.getKey());
tag.setValue(tagInfo.getValue());
existingTags.add(tag);
}
}
Set<EC2Tag> newTags = Sets.newLinkedHashSet();
if (newAction.properties.getTags() != null) {
newTags.addAll(newAction.properties.getTags());
}
List<EC2Tag> newStackTags = TagHelper.getEC2StackTags(newAction.getStackEntity());
if (newStackTags != null) {
newTags.addAll(newStackTags);
}
TagHelper.checkReservedEC2TemplateTags(newTags);
// add only 'new' tags
Set<EC2Tag> onlyNewTags = Sets.difference(newTags, existingTags);
if (!onlyNewTags.isEmpty()) {
CreateTagsType createTagsType = MessageHelper.createMessage(CreateTagsType.class, newAction.info.getEffectiveUserId());
createTagsType.setResourcesSet(Lists.newArrayList(newAction.info.getPhysicalResourceId()));
createTagsType.setTagSet(EC2Helper.createTagSet(onlyNewTags));
AsyncRequests.<CreateTagsType, CreateTagsResponseType>sendSync(configuration, createTagsType);
}
// Get old tags...
Set<EC2Tag> oldTags = Sets.newLinkedHashSet();
if (oldAction.properties.getTags() != null) {
oldTags.addAll(oldAction.properties.getTags());
}
List<EC2Tag> oldStackTags = TagHelper.getEC2StackTags(oldAction.getStackEntity());
if (oldStackTags != null) {
oldTags.addAll(oldStackTags);
}
// remove only the old tags that are not new and that exist
Set<EC2Tag> tagsToRemove = Sets.intersection(oldTags, Sets.difference(existingTags, newTags));
if (!tagsToRemove.isEmpty()) {
DeleteTagsType deleteTagsType = MessageHelper.createMessage(DeleteTagsType.class, newAction.info.getEffectiveUserId());
deleteTagsType.setResourcesSet(Lists.newArrayList(newAction.info.getPhysicalResourceId()));
deleteTagsType.setTagSet(EC2Helper.deleteTagSet(tagsToRemove));
AsyncRequests.<DeleteTagsType, DeleteTagsResponseType>sendSync(configuration, deleteTagsType);
}
return newAction;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
@Override
public ResourceProperties getResourceProperties() {
return properties;
}
@Override
public void setResourceProperties(ResourceProperties resourceProperties) {
properties = (AWSEC2VolumeProperties) resourceProperties;
}
@Override
public ResourceInfo getResourceInfo() {
return info;
}
@Override
public void setResourceInfo(ResourceInfo resourceInfo) {
info = (AWSEC2VolumeResourceInfo) resourceInfo;
}
}