/************************************************************************* * 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.cloudformation.ValidationErrorException; import com.eucalyptus.cloudformation.resources.ResourceAction; import com.eucalyptus.cloudformation.resources.ResourceInfo; import com.eucalyptus.cloudformation.resources.ResourceProperties; import com.eucalyptus.cloudformation.resources.standard.info.AWSEC2RouteResourceInfo; import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSEC2RouteProperties; 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.Compute; import com.eucalyptus.compute.common.CreateRouteResponseType; import com.eucalyptus.compute.common.CreateRouteType; import com.eucalyptus.compute.common.DeleteRouteResponseType; import com.eucalyptus.compute.common.DeleteRouteType; import com.eucalyptus.compute.common.DescribeRouteTablesResponseType; import com.eucalyptus.compute.common.DescribeRouteTablesType; import com.eucalyptus.compute.common.Filter; import com.eucalyptus.compute.common.ReplaceRouteResponseType; import com.eucalyptus.compute.common.ReplaceRouteType; import com.eucalyptus.compute.common.RouteTableType; import com.eucalyptus.compute.common.RouteType; import com.eucalyptus.util.async.AsyncRequests; import com.fasterxml.jackson.databind.node.TextNode; import com.google.common.base.Strings; import com.google.common.collect.Lists; import javax.annotation.Nullable; import java.util.List; import java.util.Objects; /** * Created by ethomas on 2/3/14. */ public class AWSEC2RouteResourceAction extends StepBasedResourceAction { private AWSEC2RouteProperties properties = new AWSEC2RouteProperties(); private AWSEC2RouteResourceInfo info = new AWSEC2RouteResourceInfo(); public AWSEC2RouteResourceAction() { 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; AWSEC2RouteResourceAction otherAction = (AWSEC2RouteResourceAction) resourceAction; if (!Objects.equals(properties.getDestinationCidrBlock(), otherAction.properties.getDestinationCidrBlock())) { updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT); } if (!Objects.equals(properties.getGatewayId(), otherAction.properties.getGatewayId())) { updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION); } if (!Objects.equals(properties.getInstanceId(), otherAction.properties.getInstanceId())) { updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION); } if (!Objects.equals(properties.getNatGatewayId(), otherAction.properties.getNatGatewayId())) { updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION); } if (!Objects.equals(properties.getNetworkInterfaceId(), otherAction.properties.getNetworkInterfaceId())) { updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION); } if (!Objects.equals(properties.getRouteTableId(), otherAction.properties.getRouteTableId())) { updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT); } if (!Objects.equals(properties.getVpcPeeringConnectionId(), otherAction.properties.getVpcPeeringConnectionId())) { updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION); } return updateType; } private enum CreateSteps implements Step { CREATE_ROUTE { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSEC2RouteResourceAction action = (AWSEC2RouteResourceAction) resourceAction; ServiceConfiguration configuration = Topology.lookup(Compute.class); // property validation action.validateProperties(); // Make sure route table exists. if (action.properties.getRouteTableId().isEmpty()) { throw new ValidationErrorException("RouteTableId is a required field"); } DescribeRouteTablesType describeRouteTablesType = MessageHelper.createMessage(DescribeRouteTablesType.class, action.info.getEffectiveUserId()); describeRouteTablesType.getFilterSet( ).add( Filter.filter( "route-table-id", action.properties.getRouteTableId( ) ) ); DescribeRouteTablesResponseType describeRouteTablesResponseType = AsyncRequests.sendSync(configuration, describeRouteTablesType); if (describeRouteTablesResponseType.getRouteTableSet() == null || describeRouteTablesResponseType.getRouteTableSet().getItem() == null || describeRouteTablesResponseType.getRouteTableSet().getItem().isEmpty()) { throw new ValidationErrorException("No such route table with id '" + action.properties.getRouteTableId()); } CreateRouteType createRouteType = MessageHelper.createMessage(CreateRouteType.class, action.info.getEffectiveUserId()); createRouteType.setRouteTableId(action.properties.getRouteTableId()); if (!Strings.isNullOrEmpty(action.properties.getGatewayId())) { createRouteType.setGatewayId(action.properties.getGatewayId()); } if (!Strings.isNullOrEmpty(action.properties.getInstanceId())) { createRouteType.setInstanceId(action.properties.getInstanceId()); } if (!Strings.isNullOrEmpty(action.properties.getVpcPeeringConnectionId())) { createRouteType.setVpcPeeringConnectionId(action.properties.getVpcPeeringConnectionId()); } if (!Strings.isNullOrEmpty(action.properties.getNatGatewayId())) { createRouteType.setNatGatewayId(action.properties.getNatGatewayId()); } if (!Strings.isNullOrEmpty(action.properties.getNetworkInterfaceId())) { createRouteType.setNetworkInterfaceId(action.properties.getNetworkInterfaceId()); } createRouteType.setDestinationCidrBlock(action.properties.getDestinationCidrBlock()); CreateRouteResponseType createRouteResponseType = AsyncRequests.<CreateRouteType, CreateRouteResponseType>sendSync(configuration, createRouteType); action.info.setPhysicalResourceId(action.getDefaultPhysicalResourceId()); action.info.setCreatedEnoughToDelete(true); action.info.setReferenceValueJson(JsonHelper.getStringFromJsonNode(new TextNode(action.info.getPhysicalResourceId()))); return action; } }; @Nullable @Override public Integer getTimeout() { return null; } } private enum DeleteSteps implements Step { DELETE_ROUTE { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSEC2RouteResourceAction action = (AWSEC2RouteResourceAction) resourceAction; ServiceConfiguration configuration = Topology.lookup(Compute.class); if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return action; DescribeRouteTablesType describeRouteTablesType = MessageHelper.createMessage(DescribeRouteTablesType.class, action.info.getEffectiveUserId()); describeRouteTablesType.getFilterSet( ).add( Filter.filter( "route-table-id", action.properties.getRouteTableId( ) ) ); DescribeRouteTablesResponseType describeRouteTablesResponseType = AsyncRequests.sendSync(configuration, describeRouteTablesType); if (describeRouteTablesResponseType.getRouteTableSet() == null || describeRouteTablesResponseType.getRouteTableSet().getItem() == null || describeRouteTablesResponseType.getRouteTableSet().getItem().isEmpty()) { return action; } // see if the route is there... boolean foundRoute = false; for (RouteTableType routeTableType: describeRouteTablesResponseType.getRouteTableSet().getItem()) { if (!action.properties.getRouteTableId().equals(routeTableType.getRouteTableId())) continue; if (routeTableType.getRouteSet() == null || routeTableType.getRouteSet().getItem() == null || routeTableType.getRouteSet().getItem().isEmpty()) { continue; // no routes } for (RouteType routeType : routeTableType.getRouteSet().getItem()) { if (action.equalRoutes(action.properties, routeType)) { foundRoute = true; break; } } } if (!foundRoute) return action; DeleteRouteType deleteRouteType = MessageHelper.createMessage(DeleteRouteType.class, action.info.getEffectiveUserId()); deleteRouteType.setRouteTableId(action.properties.getRouteTableId()); deleteRouteType.setDestinationCidrBlock(action.properties.getDestinationCidrBlock()); DeleteRouteResponseType deleteRouteResponseType = AsyncRequests.<DeleteRouteType, DeleteRouteResponseType>sendSync(configuration, deleteRouteType); return action; } }; @Nullable @Override public Integer getTimeout() { return null; } } private enum UpdateNoInterruptionSteps implements UpdateStep { UPDATE_ROUTE { @Override public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception { AWSEC2RouteResourceAction oldAction = (AWSEC2RouteResourceAction) oldResourceAction; AWSEC2RouteResourceAction newAction = (AWSEC2RouteResourceAction) newResourceAction; ServiceConfiguration configuration = Topology.lookup(Compute.class); // property validation newAction.validateProperties(); // Make sure route table exists. if (newAction.properties.getRouteTableId().isEmpty()) { throw new ValidationErrorException("RouteTableId is a required field"); } DescribeRouteTablesType describeRouteTablesType = MessageHelper.createMessage(DescribeRouteTablesType.class, newAction.info.getEffectiveUserId()); describeRouteTablesType.getFilterSet( ).add( Filter.filter( "route-table-id", newAction.properties.getRouteTableId( ) ) ); DescribeRouteTablesResponseType describeRouteTablesResponseType = AsyncRequests.sendSync(configuration, describeRouteTablesType); if (describeRouteTablesResponseType.getRouteTableSet() == null || describeRouteTablesResponseType.getRouteTableSet().getItem() == null || describeRouteTablesResponseType.getRouteTableSet().getItem().isEmpty()) { throw new ValidationErrorException("No such route table with id '" + newAction.properties.getRouteTableId()); } ReplaceRouteType replaceRouteType = MessageHelper.createMessage(ReplaceRouteType.class, newAction.info.getEffectiveUserId()); replaceRouteType.setRouteTableId(newAction.properties.getRouteTableId()); if (!Strings.isNullOrEmpty(newAction.properties.getGatewayId())) { replaceRouteType.setGatewayId(newAction.properties.getGatewayId()); } if (!Strings.isNullOrEmpty(newAction.properties.getInstanceId())) { replaceRouteType.setInstanceId(newAction.properties.getInstanceId()); } if (!Strings.isNullOrEmpty(newAction.properties.getVpcPeeringConnectionId())) { replaceRouteType.setVpcPeeringConnectionId(newAction.properties.getVpcPeeringConnectionId()); } if (!Strings.isNullOrEmpty(newAction.properties.getNatGatewayId())) { replaceRouteType.setNatGatewayId(newAction.properties.getNatGatewayId()); } if (!Strings.isNullOrEmpty(newAction.properties.getNetworkInterfaceId())) { replaceRouteType.setNetworkInterfaceId(newAction.properties.getNetworkInterfaceId()); } replaceRouteType.setDestinationCidrBlock(newAction.properties.getDestinationCidrBlock()); ReplaceRouteResponseType replaceRouteResponseType = AsyncRequests.<ReplaceRouteType, ReplaceRouteResponseType>sendSync(configuration, replaceRouteType); return newAction; } }; @Nullable @Override public Integer getTimeout() { return null; } } @Override public ResourceProperties getResourceProperties() { return properties; } @Override public void setResourceProperties(ResourceProperties resourceProperties) { properties = (AWSEC2RouteProperties) resourceProperties; } @Override public ResourceInfo getResourceInfo() { return info; } @Override public void setResourceInfo(ResourceInfo resourceInfo) { info = (AWSEC2RouteResourceInfo) resourceInfo; } private void validateProperties() throws ValidationErrorException { // You must provide only one of the following: a GatewayID, InstanceID, NatGatewayIdm NetworkInterfaceId, or VpcPeeringConnectionId. List<String> oneOfTheseParams = Lists.newArrayList(properties.getGatewayId(), properties.getInstanceId(), properties.getNatGatewayId(), properties.getNetworkInterfaceId(), properties.getVpcPeeringConnectionId()); int numNonNullOrEmpty = 0; for (String item: oneOfTheseParams) { if (!Strings.isNullOrEmpty(item)) numNonNullOrEmpty++; } if (numNonNullOrEmpty != 1) { throw new ValidationErrorException("Exactly one of GatewayID, InstanceID, NatGatewayId, NetworkInterfaceId, or VpcPeeringConnectionId must be specified"); } } private boolean equalRoutes(AWSEC2RouteProperties properties, RouteType routeType) { if (!properties.getDestinationCidrBlock().equals(routeType.getDestinationCidrBlock())) return false; if (differentIfNotNullOrEmpty(properties.getInstanceId(), routeType.getInstanceId())) return false; if (differentIfNotNullOrEmpty(properties.getGatewayId(), routeType.getGatewayId())) return false; if (differentIfNotNullOrEmpty(properties.getNatGatewayId(), routeType.getNatGatewayId())) return false; if (differentIfNotNullOrEmpty(properties.getNetworkInterfaceId(), routeType.getNetworkInterfaceId())) return false; if (differentIfNotNullOrEmpty(properties.getVpcPeeringConnectionId(), routeType.getVpcPeeringConnectionId())) return false; return true; } private boolean differentIfNotNullOrEmpty(String s1, String s2) { if (Strings.isNullOrEmpty(s1) && Strings.isNullOrEmpty(s2)) return false; if (Strings.isNullOrEmpty(s1) != Strings.isNullOrEmpty(s2)) return true; return !s1.equals(s2); } }