/*************************************************************************
* Copyright 2009-2016 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.AWSEC2NetworkInterfaceAttachmentResourceInfo;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSEC2NetworkInterfaceAttachmentProperties;
import com.eucalyptus.cloudformation.template.JsonHelper;
import com.eucalyptus.cloudformation.util.MessageHelper;
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.AttachNetworkInterfaceResponseType;
import com.eucalyptus.compute.common.AttachNetworkInterfaceType;
import com.eucalyptus.compute.common.Compute;
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.DetachNetworkInterfaceResponseType;
import com.eucalyptus.compute.common.DetachNetworkInterfaceType;
import com.eucalyptus.compute.common.ModifyNetworkInterfaceAttachmentType;
import com.eucalyptus.compute.common.ModifyNetworkInterfaceAttributeResponseType;
import com.eucalyptus.compute.common.ModifyNetworkInterfaceAttributeType;
import com.eucalyptus.compute.common.NetworkInterfaceIdSetItemType;
import com.eucalyptus.compute.common.NetworkInterfaceIdSetType;
import com.eucalyptus.configurable.ConfigurableField;
import com.eucalyptus.util.async.AsyncExceptions;
import com.eucalyptus.util.async.AsyncExceptions.AsyncWebServiceError;
import com.eucalyptus.util.async.AsyncRequests;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Objects;
/**
* Created by ethomas on 2/3/14.
*/
public class AWSEC2NetworkInterfaceAttachmentResourceAction extends StepBasedResourceAction {
private AWSEC2NetworkInterfaceAttachmentProperties properties = new AWSEC2NetworkInterfaceAttachmentProperties( );
private AWSEC2NetworkInterfaceAttachmentResourceInfo info = new AWSEC2NetworkInterfaceAttachmentResourceInfo( );
@ConfigurableField(initial = "300", description = "The amount of time (in seconds) to wait for a network interface to be attached during createor update)")
public static volatile Integer NETWORK_INTERFACE_ATTACHMENT_MAX_CREATE_OR_UPDATE_RETRY_SECS = 300;
@ConfigurableField(initial = "300", description = "The amount of time (in seconds) to wait for a network interface to detach during delete or update)")
public static volatile Integer NETWORK_INTERFACE_DETACHMENT_MAX_DELETE_OR_UPDATE_RETRY_SECS = 300;
public AWSEC2NetworkInterfaceAttachmentResourceAction() {
super(fromEnum(CreateSteps.class), fromEnum(DeleteSteps.class), fromUpdateEnum(UpdateNoInterruptionSteps.class), null);
}
@Override
public UpdateType getUpdateType(ResourceAction resourceAction, boolean stackTagsChanged) throws ValidationErrorException {
UpdateType updateType = info.supportsTags() && stackTagsChanged ? UpdateType.NO_INTERRUPTION : UpdateType.NONE;
AWSEC2NetworkInterfaceAttachmentResourceAction otherAction = (AWSEC2NetworkInterfaceAttachmentResourceAction) resourceAction;
if (!Objects.equals(properties.getDeleteOnTermination(), otherAction.properties.getDeleteOnTermination())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getDeviceIndex(), otherAction.properties.getDeviceIndex())) {
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.getNetworkInterfaceId(), otherAction.properties.getNetworkInterfaceId())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
return updateType;
}
private enum CreateSteps implements Step {
CREATE_NETWORK_INTERFACE_ATTACHMENT {
@Override
public ResourceAction perform( final ResourceAction resourceAction ) throws Exception {
final AWSEC2NetworkInterfaceAttachmentResourceAction action =
(AWSEC2NetworkInterfaceAttachmentResourceAction) resourceAction;
final ServiceConfiguration configuration = Topology.lookup( Compute.class );
return createNetworkInterfaceAttachment(action, configuration);
}
},
SET_ATTRIBUTES {
@Override
public ResourceAction perform( final ResourceAction resourceAction ) throws Exception {
final AWSEC2NetworkInterfaceAttachmentResourceAction action =
(AWSEC2NetworkInterfaceAttachmentResourceAction) resourceAction;
final ServiceConfiguration configuration = Topology.lookup( Compute.class );
return setAttributes(action, configuration);
}
},
WAIT_UNTIL_ATTACHED {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2NetworkInterfaceAttachmentResourceAction action = (AWSEC2NetworkInterfaceAttachmentResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
return waitUntilAttached(action, configuration);
}
@Override
public Integer getTimeout() {
return NETWORK_INTERFACE_ATTACHMENT_MAX_CREATE_OR_UPDATE_RETRY_SECS;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private static void throwNotAttachedMessage(String networkInterfaceId, String instanceId) throws RetryAfterConditionCheckFailedException {
throw new RetryAfterConditionCheckFailedException("Network interface " + networkInterfaceId + " not yet attached to instance " + instanceId);
}
private static ResourceAction waitUntilAttached(AWSEC2NetworkInterfaceAttachmentResourceAction action, ServiceConfiguration configuration) throws Exception {
DescribeNetworkInterfacesType describeNetworkInterfacesType = MessageHelper.createMessage(DescribeNetworkInterfacesType.class, action.info.getEffectiveUserId());
describeNetworkInterfacesType.setNetworkInterfaceIdSet(action.convertNetworkInterfaceIdSet(action.properties.getNetworkInterfaceId()));
DescribeNetworkInterfacesResponseType describeNetworkInterfacesResponseType = AsyncRequests.<DescribeNetworkInterfacesType, DescribeNetworkInterfacesResponseType>sendSync(configuration, describeNetworkInterfacesType);
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet() == null || describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem() == null ||
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().isEmpty()) {
throwNotAttachedMessage(action.properties.getNetworkInterfaceId(), action.properties.getInstanceId());
}
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().get(0).getAttachment() == null ||
!"attached".equals(describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().get(0).getAttachment().getStatus())) {
throwNotAttachedMessage(action.properties.getNetworkInterfaceId(), action.properties.getInstanceId());
}
return action;
}
private static ResourceAction setAttributes(AWSEC2NetworkInterfaceAttachmentResourceAction action, ServiceConfiguration configuration) throws Exception {
final ModifyNetworkInterfaceAttachmentType attachment = new ModifyNetworkInterfaceAttachmentType( );
attachment.setAttachmentId( action.info.getPhysicalResourceId( ) );
attachment.setDeleteOnTermination( action.properties.getDeleteOnTermination( ) != null
? action.properties.getDeleteOnTermination() : Boolean.TRUE);
final ModifyNetworkInterfaceAttributeType modifyNetworkInterfaceAttributeType =
MessageHelper.createMessage(ModifyNetworkInterfaceAttributeType.class, action.info.getEffectiveUserId());
modifyNetworkInterfaceAttributeType.setNetworkInterfaceId( action.properties.getNetworkInterfaceId( ) );
modifyNetworkInterfaceAttributeType.setAttachment( attachment );
AsyncRequests.<ModifyNetworkInterfaceAttributeType, ModifyNetworkInterfaceAttributeResponseType>sendSync(
configuration,
modifyNetworkInterfaceAttributeType);
return action;
}
private static ResourceAction createNetworkInterfaceAttachment(AWSEC2NetworkInterfaceAttachmentResourceAction action, ServiceConfiguration configuration) throws Exception {
final AttachNetworkInterfaceType attachNetworkInterfaceType =
MessageHelper.createMessage(AttachNetworkInterfaceType.class, action.info.getEffectiveUserId());
attachNetworkInterfaceType.setDeviceIndex( action.properties.getDeviceIndex( ) );
attachNetworkInterfaceType.setInstanceId( action.properties.getInstanceId( ) );
attachNetworkInterfaceType.setNetworkInterfaceId( action.properties.getNetworkInterfaceId( ) );
try {
final AttachNetworkInterfaceResponseType attachNetworkInterfaceResponseType =
AsyncRequests.sendSync(configuration, attachNetworkInterfaceType);
final String attachmentId = attachNetworkInterfaceResponseType.getAttachmentId( );
action.info.setPhysicalResourceId( attachmentId );
action.info.setCreatedEnoughToDelete(true);
action.info.setReferenceValueJson( JsonHelper.getStringFromJsonNode(new TextNode(attachmentId)) );
} catch ( final Exception e ) {
final Optional<AsyncWebServiceError> error = AsyncExceptions.asWebServiceError( e );
if ( error.isPresent( ) ) switch ( Strings.nullToEmpty(error.get().getCode()) ) {
case "InvalidInstanceID.NotFound":
throw new ValidationErrorException( "No such instance " + action.properties.getInstanceId( ) );
case "InvalidNetworkInterfaceID.NotFound":
throw new ValidationErrorException( "No such network interface " + action.properties.getNetworkInterfaceId( ) );
case "InvalidParameterValue":
throw new ValidationErrorException( "Error attaching network interface - " + e.getMessage( ) );
}
throw e;
}
return action;
}
private enum DeleteSteps implements Step {
DELETE_NETWORK_INTERFACE_ATTACHMENT {
@Override
public ResourceAction perform( final ResourceAction resourceAction ) throws Exception {
final AWSEC2NetworkInterfaceAttachmentResourceAction action =
(AWSEC2NetworkInterfaceAttachmentResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if ( !Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete()) ) return action;
if (notCreatedOrNoInstanceOrNoNetworkInterface(action, configuration)) return action;
return deleteNetworkInterfaceAttachment(action, configuration);
}
},
WAIT_UNTIL_DETACHED {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2NetworkInterfaceAttachmentResourceAction action = (AWSEC2NetworkInterfaceAttachmentResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if ( !Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete()) ) return action;
if (notCreatedOrNoInstanceOrNoNetworkInterface(action, configuration)) return action;
return waitUntilDetached(action, configuration);
}
@Override
public Integer getTimeout() {
return NETWORK_INTERFACE_DETACHMENT_MAX_DELETE_OR_UPDATE_RETRY_SECS;
}
};
@Nullable
@Override
public Integer getTimeout( ) {
return null;
}
}
private enum UpdateNoInterruptionSteps implements UpdateStep {
DELETE_NETWORK_INTERFACE_ATTACHMENT {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2NetworkInterfaceAttachmentResourceAction oldAction = (AWSEC2NetworkInterfaceAttachmentResourceAction) oldResourceAction;
AWSEC2NetworkInterfaceAttachmentResourceAction newAction = (AWSEC2NetworkInterfaceAttachmentResourceAction) newResourceAction;
if (deviceInstanceOrNetworkInterfaceIsDifferent(oldAction, newAction)) {
ServiceConfiguration configuration = Topology.lookup(Compute.class);
deleteNetworkInterfaceAttachment(oldAction, configuration);
}
return newAction;
}
},
WAIT_UNTIL_DETACHED {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2NetworkInterfaceAttachmentResourceAction oldAction = (AWSEC2NetworkInterfaceAttachmentResourceAction) oldResourceAction;
AWSEC2NetworkInterfaceAttachmentResourceAction newAction = (AWSEC2NetworkInterfaceAttachmentResourceAction) newResourceAction;
if (deviceInstanceOrNetworkInterfaceIsDifferent(oldAction, newAction)) {
ServiceConfiguration configuration = Topology.lookup(Compute.class);
waitUntilDetached(oldAction, configuration);
}
return newAction;
}
@Override
public Integer getTimeout() {
return NETWORK_INTERFACE_DETACHMENT_MAX_DELETE_OR_UPDATE_RETRY_SECS;
}
},
CREATE_NETWORK_INTERFACE_ATTACHMENT {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2NetworkInterfaceAttachmentResourceAction oldAction = (AWSEC2NetworkInterfaceAttachmentResourceAction) oldResourceAction;
AWSEC2NetworkInterfaceAttachmentResourceAction newAction = (AWSEC2NetworkInterfaceAttachmentResourceAction) newResourceAction;
if (deviceInstanceOrNetworkInterfaceIsDifferent(oldAction, newAction)) {
final ServiceConfiguration configuration = Topology.lookup(Compute.class);
return createNetworkInterfaceAttachment(newAction, configuration);
}
return newAction;
}
},
WAIT_UNTIL_ATTACHED {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2NetworkInterfaceAttachmentResourceAction oldAction = (AWSEC2NetworkInterfaceAttachmentResourceAction) oldResourceAction;
AWSEC2NetworkInterfaceAttachmentResourceAction newAction = (AWSEC2NetworkInterfaceAttachmentResourceAction) newResourceAction;
if (deviceInstanceOrNetworkInterfaceIsDifferent(oldAction, newAction)) {
ServiceConfiguration configuration = Topology.lookup(Compute.class);
return waitUntilAttached(newAction, configuration);
}
return newAction;
}
@Override
public Integer getTimeout() {
return NETWORK_INTERFACE_ATTACHMENT_MAX_CREATE_OR_UPDATE_RETRY_SECS;
}
},
SET_ATTRIBUTES {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2NetworkInterfaceAttachmentResourceAction oldAction = (AWSEC2NetworkInterfaceAttachmentResourceAction) oldResourceAction;
AWSEC2NetworkInterfaceAttachmentResourceAction newAction = (AWSEC2NetworkInterfaceAttachmentResourceAction) newResourceAction;
final ServiceConfiguration configuration = Topology.lookup( Compute.class );
return setAttributes(newAction, configuration);
}
};
private static boolean deviceInstanceOrNetworkInterfaceIsDifferent(AWSEC2NetworkInterfaceAttachmentResourceAction oldAction, AWSEC2NetworkInterfaceAttachmentResourceAction newAction) {
return !(
Objects.equals(oldAction.properties.getDeviceIndex(), newAction.properties.getDeviceIndex()) &&
Objects.equals(oldAction.properties.getInstanceId(), newAction.properties.getInstanceId()) &&
Objects.equals(oldAction.properties.getNetworkInterfaceId(), newAction.properties.getNetworkInterfaceId())
);
}
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private static ResourceAction waitUntilDetached(AWSEC2NetworkInterfaceAttachmentResourceAction action, ServiceConfiguration configuration) throws Exception {
DescribeNetworkInterfacesType describeNetworkInterfacesType = MessageHelper.createMessage(DescribeNetworkInterfacesType.class, action.info.getEffectiveUserId());
describeNetworkInterfacesType.setNetworkInterfaceIdSet(action.convertNetworkInterfaceIdSet(action.properties.getNetworkInterfaceId()));
DescribeNetworkInterfacesResponseType describeNetworkInterfacesResponseType = AsyncRequests.<DescribeNetworkInterfacesType, DescribeNetworkInterfacesResponseType>sendSync(configuration, describeNetworkInterfacesType);
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet() == null || describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem() == null ||
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().isEmpty()) {
return action;
}
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().get(0).getAttachment() == null ||
!describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().get(0).getAttachment().getAttachmentId().equals(action.info.getPhysicalResourceId())) {
return action; // must be attached to something else
}
if ("detached".equals(describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().get(0).getAttachment().getStatus())) {
return action; // detached
}
throw new RetryAfterConditionCheckFailedException("Network interface " + action.properties.getNetworkInterfaceId() + " is not yet detached from instance " + action.properties.getInstanceId());
}
private static ResourceAction deleteNetworkInterfaceAttachment(AWSEC2NetworkInterfaceAttachmentResourceAction action, ServiceConfiguration configuration) throws Exception {
final DetachNetworkInterfaceType detachNetworkInterfaceType =
MessageHelper.createMessage(DetachNetworkInterfaceType.class, action.info.getEffectiveUserId());
detachNetworkInterfaceType.setAttachmentId( action.info.getPhysicalResourceId( ) );
try {
AsyncRequests.<DetachNetworkInterfaceType, DetachNetworkInterfaceResponseType>sendSync(
configuration,
detachNetworkInterfaceType
);
} catch ( final Exception e ) {
final Optional<AsyncWebServiceError> error = AsyncExceptions.asWebServiceError( e );
if ( !error.isPresent( ) || !"InvalidAttachmentID.NotFound".equals( error.get( ).getCode( ) ) ) {
throw e;
}
}
return action;
}
@Override
public ResourceProperties getResourceProperties( ) {
return properties;
}
@Override
public void setResourceProperties( final ResourceProperties resourceProperties ) {
properties = (AWSEC2NetworkInterfaceAttachmentProperties) resourceProperties;
}
@Override
public ResourceInfo getResourceInfo( ) {
return info;
}
@Override
public void setResourceInfo( final ResourceInfo resourceInfo ) {
info = (AWSEC2NetworkInterfaceAttachmentResourceInfo) resourceInfo;
}
private static boolean notCreatedOrNoInstanceOrNoNetworkInterface(AWSEC2NetworkInterfaceAttachmentResourceAction action, ServiceConfiguration configuration) throws Exception {
if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return true;
try {
final DescribeInstancesType describeInstancesType = MessageHelper.createMessage(DescribeInstancesType.class, action.info.getEffectiveUserId());
describeInstancesType.setInstancesSet(Lists.newArrayList(action.properties.getInstanceId()));
DescribeInstancesResponseType describeInstancesResponseType = AsyncRequests.<DescribeInstancesType,DescribeInstancesResponseType> sendSync(configuration, describeInstancesType);
if (describeInstancesResponseType.getReservationSet() == null || describeInstancesResponseType.getReservationSet().isEmpty()) {
return true; // can't be attached to a nonexistent instance;
}
} catch ( final Exception e ) {
final Optional<AsyncWebServiceError> error = AsyncExceptions.asWebServiceError( e );
if ( error.isPresent( ) ) switch ( Strings.nullToEmpty(error.get().getCode()) ) {
case "InvalidInstanceID.NotFound":
return true; // can't be attached to a nonexistent instance;
}
throw e;
}
try {
final DescribeNetworkInterfacesType describeNetworkInterfacesType = MessageHelper.createMessage(DescribeNetworkInterfacesType.class, action.info.getEffectiveUserId());
describeNetworkInterfacesType.setNetworkInterfaceIdSet(action.convertNetworkInterfaceIdSet(action.properties.getNetworkInterfaceId()));
DescribeNetworkInterfacesResponseType describeNetworkInterfacesResponseType = AsyncRequests.<DescribeNetworkInterfacesType, DescribeNetworkInterfacesResponseType>sendSync(configuration, describeNetworkInterfacesType);
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet() == null || describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem() == null ||
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().isEmpty()) {
return true; // network interface can't be attached if it doesnt exist
}
} catch ( final Exception e ) {
final Optional<AsyncWebServiceError> error = AsyncExceptions.asWebServiceError( e );
if ( error.isPresent( ) ) switch ( Strings.nullToEmpty(error.get().getCode()) ) {
case "InvalidNetworkInterfaceID.NotFound":
return true; // network interface can't be attached if it doesnt exist
}
throw e;
}
return false;
}
private NetworkInterfaceIdSetType convertNetworkInterfaceIdSet(String networkInterfaceId) {
NetworkInterfaceIdSetType networkInterfaceIdSetType = new NetworkInterfaceIdSetType();
ArrayList<NetworkInterfaceIdSetItemType> item = Lists.newArrayList();
NetworkInterfaceIdSetItemType networkInterfaceIdSetItemType = new NetworkInterfaceIdSetItemType();
networkInterfaceIdSetItemType.setNetworkInterfaceId(networkInterfaceId);
item.add(networkInterfaceIdSetItemType);
networkInterfaceIdSetType.setItem(item);
return networkInterfaceIdSetType;
}
}