/*************************************************************************
* 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.entity.StackResourceEntity;
import com.eucalyptus.cloudformation.entity.StackResourceEntityManager;
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.info.AWSEC2EIPAssociationResourceInfo;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSEC2EIPAssociationProperties;
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.AssociateAddressResponseType;
import com.eucalyptus.compute.common.AssociateAddressType;
import com.eucalyptus.compute.common.Compute;
import com.eucalyptus.compute.common.DescribeAddressesResponseType;
import com.eucalyptus.compute.common.DescribeAddressesType;
import com.eucalyptus.compute.common.DescribeInstancesResponseType;
import com.eucalyptus.compute.common.DescribeInstancesType;
import com.eucalyptus.compute.common.DescribeNetworkInterfacesResponseType;
import com.eucalyptus.compute.common.DescribeNetworkInterfacesType;
import com.eucalyptus.compute.common.DisassociateAddressResponseType;
import com.eucalyptus.compute.common.DisassociateAddressType;
import com.eucalyptus.compute.common.Filter;
import com.eucalyptus.compute.common.NetworkInterfaceType;
import com.eucalyptus.util.async.AsyncRequests;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.collect.Lists;
import org.apache.log4j.Logger;
import javax.annotation.Nullable;
import java.util.Objects;
/**
* Created by ethomas on 2/3/14.
*/
public class AWSEC2EIPAssociationResourceAction extends StepBasedResourceAction {
private static final Logger LOG = Logger.getLogger(AWSEC2EIPAssociationResourceAction.class);
private AWSEC2EIPAssociationProperties properties = new AWSEC2EIPAssociationProperties();
private AWSEC2EIPAssociationResourceInfo info = new AWSEC2EIPAssociationResourceInfo();
public AWSEC2EIPAssociationResourceAction() {
super(fromEnum(CreateSteps.class), fromEnum(DeleteSteps.class), fromUpdateEnum(UpdateNoInterruptionSteps.class), null);
}
@Override
public boolean mustCheckUpdateTypeEvenIfNoPropertiesChanged() {
return true;
}
@Override
public UpdateType getUpdateType(ResourceAction resourceAction, boolean stackTagsChanged) {
UpdateType updateType = info.supportsTags() && stackTagsChanged ? UpdateType.NO_INTERRUPTION : UpdateType.NONE;
AWSEC2EIPAssociationResourceAction otherAction = (AWSEC2EIPAssociationResourceAction) resourceAction;
if (!Objects.equals(properties.getAllocationId(), otherAction.properties.getAllocationId())) {
// Update requires: Replacement if you also change the InstanceId or NetworkInterfaceId property. If not, update requires No interruption.
if (!Objects.equals(properties.getInstanceId(), otherAction.properties.getInstanceId()) ||
!Objects.equals(properties.getNetworkInterfaceId(), otherAction.properties.getNetworkInterfaceId())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
} else {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
}
if (!Objects.equals(properties.getEip(), otherAction.properties.getEip())) {
// Update requires: Replacement if you also change the InstanceId or NetworkInterfaceId property. If not, update requires No interruption.
if (!Objects.equals(properties.getInstanceId(), otherAction.properties.getInstanceId()) ||
!Objects.equals(properties.getNetworkInterfaceId(), otherAction.properties.getNetworkInterfaceId())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
} else {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
}
// Might be a little redundancy here, but trying to adhere to the instructions exactly rather than optimize
if (!Objects.equals(properties.getInstanceId(), otherAction.properties.getInstanceId())) {
// Update requires: Replacement if you also change the AllocationId or EIP property. If not, update requires No interruption.
if (!Objects.equals(properties.getAllocationId(), otherAction.properties.getAllocationId()) ||
!Objects.equals(properties.getEip(), otherAction.properties.getEip())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
} else {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
}
if (!Objects.equals(properties.getNetworkInterfaceId(), otherAction.properties.getNetworkInterfaceId())) {
// Update requires: Replacement if you also change the AllocationId or EIP property. If not, update requires No interruption.
if (!Objects.equals(properties.getAllocationId(), otherAction.properties.getAllocationId()) ||
!Objects.equals(properties.getEip(), otherAction.properties.getEip())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
} else {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
}
if (!Objects.equals(properties.getPrivateIpAddress(), otherAction.properties.getPrivateIpAddress())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
// finally update if the 'instance' we are associated was either NO_INTERRUPTION or SOME_INTERRUPTION updated
if (Objects.equals(properties.getInstanceId(), otherAction.properties.getInstanceId()) ) {
if (wasInstanceUpdated(otherAction)) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
}
return updateType;
}
private boolean wasInstanceUpdated(AWSEC2EIPAssociationResourceAction action) {
if (action.properties.getInstanceId() == null) return false;
StackResourceEntity stackResourceEntity = StackResourceEntityManager.getStackResourceByPhysicalResourceId(action.stackEntity.getStackId(), action.stackEntity.getAccountId(), action.properties.getInstanceId(), action.stackEntity.getStackVersion());
return (stackResourceEntity != null &&
(stackResourceEntity.getUpdateType().equals(UpdateType.NO_INTERRUPTION.toString()) || stackResourceEntity.getUpdateType().equals(UpdateType.SOME_INTERRUPTION.toString())));
}
private enum CreateSteps implements Step {
CREATE_EIP_ASSOCIATION {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2EIPAssociationResourceAction action = (AWSEC2EIPAssociationResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
AssociateAddressType associateAddressType = MessageHelper.createMessage(AssociateAddressType.class, action.info.getEffectiveUserId());
associateAddressType.setAllowReassociation(true); // to allow for no-interruption update
if (action.properties.getInstanceId() == null && action.properties.getNetworkInterfaceId() == null) {
throw new ValidationErrorException("Either instance ID or network interface id must be specified");
}
if (action.properties.getInstanceId() != null) {
DescribeInstancesType describeInstancesType = MessageHelper.createMessage(DescribeInstancesType.class, action.info.getEffectiveUserId());
describeInstancesType.getFilterSet( ).add( Filter.filter( "instance-id", action.properties.getInstanceId() ) );
DescribeInstancesResponseType describeInstancesResponseType = AsyncRequests.sendSync( configuration, describeInstancesType );
if (describeInstancesResponseType.getReservationSet() == null || describeInstancesResponseType.getReservationSet().isEmpty()) {
throw new ValidationErrorException("No such instance " + action.properties.getInstanceId());
}
associateAddressType.setInstanceId(action.properties.getInstanceId());
}
if (action.properties.getEip() != null) {
DescribeAddressesType describeAddressesType = MessageHelper.createMessage(DescribeAddressesType.class, action.info.getEffectiveUserId());
describeAddressesType.setPublicIpsSet(Lists.newArrayList(action.properties.getEip()));
DescribeAddressesResponseType describeAddressesResponseType = AsyncRequests.<DescribeAddressesType, DescribeAddressesResponseType> sendSync(configuration, describeAddressesType);
if (describeAddressesResponseType.getAddressesSet() == null || describeAddressesResponseType.getAddressesSet().isEmpty()) {
throw new ValidationErrorException("No such EIP " + action.properties.getEip());
}
associateAddressType.setPublicIp(action.properties.getEip());
}
if (action.properties.getAllocationId() != null) {
DescribeAddressesType describeAddressesType = MessageHelper.createMessage(DescribeAddressesType.class, action.info.getEffectiveUserId());
describeAddressesType.setAllocationIds(Lists.newArrayList(action.properties.getAllocationId()));
DescribeAddressesResponseType describeAddressesResponseType = AsyncRequests.<DescribeAddressesType, DescribeAddressesResponseType> sendSync(configuration, describeAddressesType);
if (describeAddressesResponseType.getAddressesSet() == null || describeAddressesResponseType.getAddressesSet().isEmpty()) {
throw new ValidationErrorException("No such allocation-id " + action.properties.getAllocationId());
}
associateAddressType.setAllocationId(action.properties.getAllocationId());
}
if (action.properties.getNetworkInterfaceId() != null) {
associateAddressType.setNetworkInterfaceId(action.properties.getNetworkInterfaceId());
}
if (action.properties.getPrivateIpAddress() != null) {
associateAddressType.setPrivateIpAddress(action.properties.getPrivateIpAddress());
}
AssociateAddressResponseType associateAddressResponseType = AsyncRequests.<AssociateAddressType, AssociateAddressResponseType> sendSync(configuration, associateAddressType);
if (action.properties.getAllocationId() != null) {
action.info.setPhysicalResourceId(associateAddressResponseType.getAssociationId());
} else {
action.info.setPhysicalResourceId(action.getDefaultPhysicalResourceId());
}
action.info.setCreatedEnoughToDelete(true);
action.info.setReferenceValueJson(JsonHelper.getStringFromJsonNode(new TextNode(action.info.getPhysicalResourceId())));
// Update the instance info
if (action.properties.getInstanceId() != null) {
EC2Helper.refreshInstanceAttributes(action.getStackEntity(), action.properties.getInstanceId(), action.info.getEffectiveUserId(), action.getStackEntity().getStackVersion());
}
// Update the instance info (if network id exists)
if (action.properties.getNetworkInterfaceId() != null) {
DescribeNetworkInterfacesType describeNetworkInterfacesType = MessageHelper.createMessage(DescribeNetworkInterfacesType.class, action.info.getEffectiveUserId());
describeNetworkInterfacesType.getFilterSet( ).add( Filter.filter( "network-interface-id", action.properties.getNetworkInterfaceId() ) );
DescribeNetworkInterfacesResponseType describeNetworkInterfacesResponseType = AsyncRequests.sendSync( configuration, describeNetworkInterfacesType);
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet() != null &&
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem() != null &&
!describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().isEmpty()) {
for (NetworkInterfaceType networkInterfaceType: describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem()) {
if (networkInterfaceType != null && networkInterfaceType.getAttachment() != null &&
networkInterfaceType.getAttachment().getDeviceIndex() == 0 &&
networkInterfaceType.getAttachment().getInstanceId() != null) {
EC2Helper.refreshInstanceAttributes(action.getStackEntity(), networkInterfaceType.getAttachment().getInstanceId(), action.info.getEffectiveUserId(), action.getStackEntity().getStackVersion());
}
}
}
}
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private enum DeleteSteps implements Step {
DELETE_EIP_ASSOCIATION {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2EIPAssociationResourceAction action = (AWSEC2EIPAssociationResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return action;
if (action.properties.getAllocationId() != null) {
DescribeAddressesType describeAddressesType = MessageHelper.createMessage(DescribeAddressesType.class, action.info.getEffectiveUserId());
describeAddressesType.setAllocationIds(Lists.newArrayList(action.properties.getAllocationId()));
DescribeAddressesResponseType describeAddressesResponseType = AsyncRequests.<DescribeAddressesType, DescribeAddressesResponseType> sendSync(configuration, describeAddressesType);
if (describeAddressesResponseType.getAddressesSet() != null && !describeAddressesResponseType.getAddressesSet().isEmpty()) {
DisassociateAddressType disassociateAddressType = MessageHelper.createMessage(DisassociateAddressType.class, action.info.getEffectiveUserId());
disassociateAddressType.setAssociationId(action.info.getPhysicalResourceId());
AsyncRequests.<DisassociateAddressType, DisassociateAddressResponseType> sendSync(configuration, disassociateAddressType);
}
}
if (action.properties.getEip() != null) {
DescribeAddressesType describeAddressesType = MessageHelper.createMessage(DescribeAddressesType.class, action.info.getEffectiveUserId());
describeAddressesType.setPublicIpsSet(Lists.newArrayList(action.properties.getEip()));
DescribeAddressesResponseType describeAddressesResponseType = AsyncRequests.<DescribeAddressesType, DescribeAddressesResponseType> sendSync(configuration, describeAddressesType);
if (describeAddressesResponseType.getAddressesSet() != null && !describeAddressesResponseType.getAddressesSet().isEmpty()) {
DisassociateAddressType disassociateAddressType = MessageHelper.createMessage(DisassociateAddressType.class, action.info.getEffectiveUserId());
disassociateAddressType.setPublicIp(action.properties.getEip());
AsyncRequests.<DisassociateAddressType, DisassociateAddressResponseType> sendSync(configuration, disassociateAddressType);
}
}
// Update the instance info
if (action.properties.getInstanceId() != null) {
EC2Helper.refreshInstanceAttributes(action.getStackEntity(), action.properties.getInstanceId(), action.info.getEffectiveUserId(), action.getStackEntity().getStackVersion());
}
// Update the instance info (if network id exists)
if (action.properties.getNetworkInterfaceId() != null) {
DescribeNetworkInterfacesType describeNetworkInterfacesType = MessageHelper.createMessage(DescribeNetworkInterfacesType.class, action.info.getEffectiveUserId());
describeNetworkInterfacesType.getFilterSet( ).add( Filter.filter( "network-interface-id", action.properties.getNetworkInterfaceId() ) );
DescribeNetworkInterfacesResponseType describeNetworkInterfacesResponseType = AsyncRequests.sendSync(configuration, describeNetworkInterfacesType);
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet() != null &&
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem() != null &&
!describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().isEmpty()) {
for (NetworkInterfaceType networkInterfaceType: describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem()) {
if (networkInterfaceType != null && networkInterfaceType.getAttachment() != null &&
networkInterfaceType.getAttachment().getDeviceIndex() == 0 &&
networkInterfaceType.getAttachment().getInstanceId() != null) {
EC2Helper.refreshInstanceAttributes(action.getStackEntity(), networkInterfaceType.getAttachment().getInstanceId(), action.info.getEffectiveUserId(), action.getStackEntity().getStackVersion());
}
}
}
}
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private enum UpdateNoInterruptionSteps implements UpdateStep {
CREATE_EIP_ASSOCIATION {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2EIPAssociationResourceAction oldAction = (AWSEC2EIPAssociationResourceAction) oldResourceAction;
AWSEC2EIPAssociationResourceAction newAction = (AWSEC2EIPAssociationResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
AssociateAddressType associateAddressType = MessageHelper.createMessage(AssociateAddressType.class, newAction.info.getEffectiveUserId());
associateAddressType.setAllowReassociation(true); // to allow for no-interruption update
if (newAction.properties.getInstanceId() == null && newAction.properties.getNetworkInterfaceId() == null) {
throw new ValidationErrorException("Either instance ID or network interface id must be specified");
}
if (newAction.properties.getInstanceId() != null) {
DescribeInstancesType describeInstancesType = MessageHelper.createMessage(DescribeInstancesType.class, newAction.info.getEffectiveUserId());
describeInstancesType.getFilterSet( ).add( Filter.filter( "instance-id", newAction.properties.getInstanceId() ) );
DescribeInstancesResponseType describeInstancesResponseType = AsyncRequests.sendSync( configuration, describeInstancesType );
if (describeInstancesResponseType.getReservationSet() == null || describeInstancesResponseType.getReservationSet().isEmpty()) {
throw new ValidationErrorException("No such instance " + newAction.properties.getInstanceId());
}
associateAddressType.setInstanceId(newAction.properties.getInstanceId());
}
if (newAction.properties.getEip() != null) {
DescribeAddressesType describeAddressesType = MessageHelper.createMessage(DescribeAddressesType.class, newAction.info.getEffectiveUserId());
describeAddressesType.setPublicIpsSet(Lists.newArrayList(newAction.properties.getEip()));
DescribeAddressesResponseType describeAddressesResponseType = AsyncRequests.<DescribeAddressesType, DescribeAddressesResponseType> sendSync(configuration, describeAddressesType);
if (describeAddressesResponseType.getAddressesSet() == null || describeAddressesResponseType.getAddressesSet().isEmpty()) {
throw new ValidationErrorException("No such EIP " + newAction.properties.getEip());
}
associateAddressType.setPublicIp(newAction.properties.getEip());
}
if (newAction.properties.getAllocationId() != null) {
DescribeAddressesType describeAddressesType = MessageHelper.createMessage(DescribeAddressesType.class, newAction.info.getEffectiveUserId());
describeAddressesType.setAllocationIds(Lists.newArrayList(newAction.properties.getAllocationId()));
DescribeAddressesResponseType describeAddressesResponseType = AsyncRequests.<DescribeAddressesType, DescribeAddressesResponseType> sendSync(configuration, describeAddressesType);
if (describeAddressesResponseType.getAddressesSet() == null || describeAddressesResponseType.getAddressesSet().isEmpty()) {
throw new ValidationErrorException("No such allocation-id " + newAction.properties.getAllocationId());
}
associateAddressType.setAllocationId(newAction.properties.getAllocationId());
}
if (newAction.properties.getNetworkInterfaceId() != null) {
associateAddressType.setNetworkInterfaceId(newAction.properties.getNetworkInterfaceId());
}
if (newAction.properties.getPrivateIpAddress() != null) {
associateAddressType.setPrivateIpAddress(newAction.properties.getPrivateIpAddress());
}
AssociateAddressResponseType associateAddressResponseType = AsyncRequests.<AssociateAddressType, AssociateAddressResponseType> sendSync(configuration, associateAddressType);
if (newAction.properties.getAllocationId() != null) {
newAction.info.setPhysicalResourceId(associateAddressResponseType.getAssociationId());
} else {
newAction.info.setPhysicalResourceId(newAction.getDefaultPhysicalResourceId());
}
newAction.info.setCreatedEnoughToDelete(true);
newAction.info.setReferenceValueJson(JsonHelper.getStringFromJsonNode(new TextNode(newAction.info.getPhysicalResourceId())));
// Update the instance info
if (newAction.properties.getInstanceId() != null) {
EC2Helper.refreshInstanceAttributes(newAction.getStackEntity(), newAction.properties.getInstanceId(), newAction.info.getEffectiveUserId(), newAction.getStackEntity().getStackVersion());
}
// Update the instance info (if network id exists)
if (newAction.properties.getNetworkInterfaceId() != null) {
DescribeNetworkInterfacesType describeNetworkInterfacesType = MessageHelper.createMessage(DescribeNetworkInterfacesType.class, newAction.info.getEffectiveUserId());
describeNetworkInterfacesType.getFilterSet( ).add( Filter.filter( "network-interface-id", newAction.properties.getNetworkInterfaceId() ) );
DescribeNetworkInterfacesResponseType describeNetworkInterfacesResponseType = AsyncRequests.sendSync( configuration, describeNetworkInterfacesType);
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet() != null &&
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem() != null &&
!describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().isEmpty()) {
for (NetworkInterfaceType networkInterfaceType: describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem()) {
if (networkInterfaceType != null && networkInterfaceType.getAttachment() != null &&
networkInterfaceType.getAttachment().getDeviceIndex() == 0 &&
networkInterfaceType.getAttachment().getInstanceId() != null) {
EC2Helper.refreshInstanceAttributes(newAction.getStackEntity(), networkInterfaceType.getAttachment().getInstanceId(), newAction.info.getEffectiveUserId(), newAction.getStackEntity().getStackVersion());
}
}
}
}
return newAction;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
@Override
public ResourceProperties getResourceProperties() {
return properties;
}
@Override
public void setResourceProperties(ResourceProperties resourceProperties) {
properties = (AWSEC2EIPAssociationProperties) resourceProperties;
}
@Override
public ResourceInfo getResourceInfo() {
return info;
}
@Override
public void setResourceInfo(ResourceInfo resourceInfo) {
info = (AWSEC2EIPAssociationResourceInfo) resourceInfo;
}
}