/*************************************************************************
* 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.autoscaling.common.msgs.DeleteTagsResponseType;
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.AWSEC2VPCResourceInfo;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSEC2VPCProperties;
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.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.AttributeBooleanValueType;
import com.eucalyptus.compute.common.Compute;
import com.eucalyptus.compute.common.CreateTagsResponseType;
import com.eucalyptus.compute.common.CreateTagsType;
import com.eucalyptus.compute.common.CreateVpcResponseType;
import com.eucalyptus.compute.common.CreateVpcType;
import com.eucalyptus.compute.common.DeleteTagsType;
import com.eucalyptus.compute.common.DeleteVpcResponseType;
import com.eucalyptus.compute.common.DeleteVpcType;
import com.eucalyptus.compute.common.DescribeNetworkAclsResponseType;
import com.eucalyptus.compute.common.DescribeNetworkAclsType;
import com.eucalyptus.compute.common.DescribeSecurityGroupsResponseType;
import com.eucalyptus.compute.common.DescribeSecurityGroupsType;
import com.eucalyptus.compute.common.DescribeTagsResponseType;
import com.eucalyptus.compute.common.DescribeTagsType;
import com.eucalyptus.compute.common.DescribeVpcsResponseType;
import com.eucalyptus.compute.common.DescribeVpcsType;
import com.eucalyptus.compute.common.Filter;
import com.eucalyptus.compute.common.ModifyVpcAttributeResponseType;
import com.eucalyptus.compute.common.ModifyVpcAttributeType;
import com.eucalyptus.compute.common.TagInfo;
import com.eucalyptus.util.async.AsyncRequests;
import com.eucalyptus.util.async.CheckedListenableFuture;
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;
/**
* Created by ethomas on 2/3/14.
*/
public class AWSEC2VPCResourceAction extends StepBasedResourceAction {
private AWSEC2VPCProperties properties = new AWSEC2VPCProperties();
private AWSEC2VPCResourceInfo info = new AWSEC2VPCResourceInfo();
public AWSEC2VPCResourceAction() {
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;
AWSEC2VPCResourceAction otherAction = (AWSEC2VPCResourceAction) resourceAction;
if (!Objects.equals(properties.getCidrBlock(), otherAction.properties.getCidrBlock())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
if (!Objects.equals(properties.getEnableDnsHostnames(), otherAction.properties.getEnableDnsHostnames())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getEnableDnsSupport(), otherAction.properties.getEnableDnsSupport())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getInstanceTenancy(), otherAction.properties.getInstanceTenancy())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
if (!Objects.equals(properties.getTags(), otherAction.properties.getTags())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
return updateType;
}
private enum CreateSteps implements Step {
CREATE_VPC {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VPCResourceAction action = (AWSEC2VPCResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
CreateVpcType createVpcType = MessageHelper.createMessage(CreateVpcType.class, action.info.getEffectiveUserId());
createVpcType.setCidrBlock(action.properties.getCidrBlock());
if (action.properties.getInstanceTenancy() == null) {
createVpcType.setInstanceTenancy("default");
} else if (!"default".equals(action.properties.getInstanceTenancy()) && !"dedicated".equals(action.properties.getInstanceTenancy())) {
throw new ValidationErrorException("InstanceTenancy must be 'dedicated' or 'default");
} else {
createVpcType.setInstanceTenancy(action.properties.getInstanceTenancy());
}
CreateVpcResponseType createVpcResponseType = AsyncRequests.<CreateVpcType,CreateVpcResponseType> sendSync(configuration, createVpcType);
action.info.setPhysicalResourceId(createVpcResponseType.getVpc().getVpcId());
action.info.setCreatedEnoughToDelete(true);
action.info.setCidrBlock(JsonHelper.getStringFromJsonNode(new TextNode( createVpcResponseType.getVpc( ).getCidrBlock( ))));
action.info.setReferenceValueJson(JsonHelper.getStringFromJsonNode(new TextNode(action.info.getPhysicalResourceId())));
return action;
}
},
SET_DNS_ENTRIES {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VPCResourceAction action = (AWSEC2VPCResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
ModifyVpcAttributeType modifyVpcAttributeType = MessageHelper.createMessage(ModifyVpcAttributeType.class, action.info.getEffectiveUserId());
boolean enableDnsSupport = true; // default value
boolean enableDnsHostnames = false; // default value
if (action.properties.getEnableDnsSupport() != null) {
enableDnsSupport = action.properties.getEnableDnsSupport();
}
if (action.properties.getEnableDnsHostnames() != null) {
enableDnsHostnames = action.properties.getEnableDnsHostnames();
}
modifyVpcAttributeType.setVpcId(action.info.getPhysicalResourceId());
modifyVpcAttributeType.setEnableDnsSupport(action.createAttributeBooleanValueType(enableDnsSupport));
modifyVpcAttributeType.setEnableDnsHostnames(action.createAttributeBooleanValueType(enableDnsHostnames));
// TODO: does the below return any errors?
AsyncRequests.<ModifyVpcAttributeType,ModifyVpcAttributeResponseType> sendSync(configuration, modifyVpcAttributeType);
return action;
}
},
CREATE_TAGS {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VPCResourceAction action = (AWSEC2VPCResourceAction) 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;
}
},
DESCRIBE_VPC_RESOURCES_TO_GET_ATTRIBUTES {
@Override
public ResourceAction perform( final ResourceAction resourceAction ) throws Exception {
AWSEC2VPCResourceAction action = (AWSEC2VPCResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
// Describe groups to find the default
final DescribeSecurityGroupsType groupsRequest = MessageHelper.createMessage(DescribeSecurityGroupsType.class, action.info.getEffectiveUserId());
groupsRequest.setSecurityGroupSet( Lists.newArrayList( "default" ) );
groupsRequest.setFilterSet( Lists.newArrayList( Filter.filter( "vpc-id", action.info.getPhysicalResourceId( ) ) ) );
final CheckedListenableFuture<DescribeSecurityGroupsResponseType> groupsFuture =
AsyncRequests.dispatch( configuration, groupsRequest );
// Describe network acls to find the default
final DescribeNetworkAclsType networkAclsRequest = MessageHelper.createMessage(DescribeNetworkAclsType.class, action.info.getEffectiveUserId());
networkAclsRequest.setFilterSet( Lists.newArrayList(
Filter.filter( "vpc-id", action.info.getPhysicalResourceId( ) ),
Filter.filter( "default", "true" )
) );
final CheckedListenableFuture<DescribeNetworkAclsResponseType> networkAclsFuture =
AsyncRequests.dispatch( configuration, networkAclsRequest );
// Record attribute values
if ( !groupsFuture.get( ).getSecurityGroupInfo( ).isEmpty( ) ) {
action.info.setDefaultSecurityGroup( JsonHelper.getStringFromJsonNode( new TextNode( groupsFuture.get( ).getSecurityGroupInfo( ).get( 0 ).getGroupId( ) ) ) );
}
if ( !networkAclsFuture.get( ).getNetworkAclSet( ).getItem( ).isEmpty( ) ) {
action.info.setDefaultNetworkAcl( JsonHelper.getStringFromJsonNode( new TextNode( networkAclsFuture.get( ).getNetworkAclSet( ).getItem( ).get( 0 ).getNetworkAclId( ) ) ) );
}
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private enum DeleteSteps implements Step {
DELETE_VPC {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2VPCResourceAction action = (AWSEC2VPCResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return action;
DescribeVpcsType describeVpcsType = MessageHelper.createMessage(DescribeVpcsType.class, action.info.getEffectiveUserId());
describeVpcsType.getFilterSet( ).add( Filter.filter( "vpc-id", action.info.getPhysicalResourceId( ) ) );
DescribeVpcsResponseType describeVpcsResponseType = AsyncRequests.sendSync( configuration, describeVpcsType );
if (describeVpcsResponseType.getVpcSet() == null ||
describeVpcsResponseType.getVpcSet().getItem() == null ||
describeVpcsResponseType.getVpcSet().getItem().isEmpty()) {
return action; // already deleted
}
DeleteVpcType deleteVpcType = MessageHelper.createMessage(DeleteVpcType.class, action.info.getEffectiveUserId());
deleteVpcType.setVpcId(action.info.getPhysicalResourceId());
AsyncRequests.<DeleteVpcType,DeleteVpcResponseType> sendSync(configuration, deleteVpcType);
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private enum UpdateNoInterruptionSteps implements UpdateStep {
UPDATE_DNS_ENTRIES {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2VPCResourceAction oldAction = (AWSEC2VPCResourceAction) oldResourceAction;
AWSEC2VPCResourceAction newAction = (AWSEC2VPCResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
ModifyVpcAttributeType modifyVpcAttributeType = MessageHelper.createMessage(ModifyVpcAttributeType.class, newAction.info.getEffectiveUserId());
boolean enableDnsSupport = true; // default value
boolean enableDnsHostnames = false; // default value
if (newAction.properties.getEnableDnsSupport() != null) {
enableDnsSupport = newAction.properties.getEnableDnsSupport();
}
if (newAction.properties.getEnableDnsHostnames() != null) {
enableDnsHostnames = newAction.properties.getEnableDnsHostnames();
}
modifyVpcAttributeType.setVpcId(newAction.info.getPhysicalResourceId());
modifyVpcAttributeType.setEnableDnsSupport(newAction.createAttributeBooleanValueType(enableDnsSupport));
modifyVpcAttributeType.setEnableDnsHostnames(newAction.createAttributeBooleanValueType(enableDnsHostnames));
// TODO: does the below return any errors?
AsyncRequests.<ModifyVpcAttributeType,ModifyVpcAttributeResponseType> sendSync(configuration, modifyVpcAttributeType);
return newAction;
}
},
UPDATE_TAGS {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2VPCResourceAction oldAction = (AWSEC2VPCResourceAction) oldResourceAction;
AWSEC2VPCResourceAction newAction = (AWSEC2VPCResourceAction) 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;
}
},
DESCRIBE_VPC_RESOURCES_TO_UPDATE_ATTRIBUTES {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2VPCResourceAction oldAction = (AWSEC2VPCResourceAction) oldResourceAction;
AWSEC2VPCResourceAction newAction = (AWSEC2VPCResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
// Describe groups to find the default
final DescribeSecurityGroupsType groupsRequest = MessageHelper.createMessage(DescribeSecurityGroupsType.class, newAction.info.getEffectiveUserId());
groupsRequest.setSecurityGroupSet(Lists.newArrayList("default"));
groupsRequest.setFilterSet(Lists.newArrayList(Filter.filter("vpc-id", newAction.info.getPhysicalResourceId())));
final CheckedListenableFuture<DescribeSecurityGroupsResponseType> groupsFuture =
AsyncRequests.dispatch(configuration, groupsRequest);
// Describe network acls to find the default
final DescribeNetworkAclsType networkAclsRequest = MessageHelper.createMessage(DescribeNetworkAclsType.class, newAction.info.getEffectiveUserId());
networkAclsRequest.setFilterSet(Lists.newArrayList(
Filter.filter("vpc-id", newAction.info.getPhysicalResourceId()),
Filter.filter("default", "true")
));
final CheckedListenableFuture<DescribeNetworkAclsResponseType> networkAclsFuture =
AsyncRequests.dispatch(configuration, networkAclsRequest);
// Record attribute values
if (!groupsFuture.get().getSecurityGroupInfo().isEmpty()) {
newAction.info.setDefaultSecurityGroup(JsonHelper.getStringFromJsonNode(new TextNode(groupsFuture.get().getSecurityGroupInfo().get(0).getGroupId())));
}
if (!networkAclsFuture.get().getNetworkAclSet().getItem().isEmpty()) {
newAction.info.setDefaultNetworkAcl(JsonHelper.getStringFromJsonNode(new TextNode(networkAclsFuture.get().getNetworkAclSet().getItem().get(0).getNetworkAclId())));
}
return newAction;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
@Override
public ResourceProperties getResourceProperties() {
return properties;
}
@Override
public void setResourceProperties(ResourceProperties resourceProperties) {
properties = (AWSEC2VPCProperties) resourceProperties;
}
@Override
public ResourceInfo getResourceInfo() {
return info;
}
@Override
public void setResourceInfo(ResourceInfo resourceInfo) {
info = (AWSEC2VPCResourceInfo) resourceInfo;
}
private AttributeBooleanValueType createAttributeBooleanValueType(boolean value) {
AttributeBooleanValueType attributeBooleanValueType = new AttributeBooleanValueType();
attributeBooleanValueType.setValue(value);
return attributeBooleanValueType;
}
}