/*************************************************************************
* 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.AWSEC2NetworkInterfaceResourceInfo;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSEC2NetworkInterfaceProperties;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.EC2Tag;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.PrivateIpAddressSpecification;
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.AssignPrivateIpAddressesSetItemRequestType;
import com.eucalyptus.compute.common.AssignPrivateIpAddressesSetRequestType;
import com.eucalyptus.compute.common.AssignPrivateIpAddressesType;
import com.eucalyptus.compute.common.AttributeBooleanValueType;
import com.eucalyptus.compute.common.Compute;
import com.eucalyptus.compute.common.CreateNetworkInterfaceResponseType;
import com.eucalyptus.compute.common.CreateNetworkInterfaceType;
import com.eucalyptus.compute.common.CreateTagsResponseType;
import com.eucalyptus.compute.common.CreateTagsType;
import com.eucalyptus.compute.common.DeleteNetworkInterfaceResponseType;
import com.eucalyptus.compute.common.DeleteNetworkInterfaceType;
import com.eucalyptus.compute.common.DeleteTagsResponseType;
import com.eucalyptus.compute.common.DeleteTagsType;
import com.eucalyptus.compute.common.DescribeNetworkInterfacesResponseType;
import com.eucalyptus.compute.common.DescribeNetworkInterfacesType;
import com.eucalyptus.compute.common.DescribeSecurityGroupsResponseType;
import com.eucalyptus.compute.common.DescribeSecurityGroupsType;
import com.eucalyptus.compute.common.DescribeSubnetsResponseType;
import com.eucalyptus.compute.common.DescribeSubnetsType;
import com.eucalyptus.compute.common.DescribeTagsResponseType;
import com.eucalyptus.compute.common.DescribeTagsType;
import com.eucalyptus.compute.common.Filter;
import com.eucalyptus.compute.common.ModifyNetworkInterfaceAttributeResponseType;
import com.eucalyptus.compute.common.ModifyNetworkInterfaceAttributeType;
import com.eucalyptus.compute.common.NetworkInterfacePrivateIpAddressesSetItemType;
import com.eucalyptus.compute.common.NullableAttributeValueType;
import com.eucalyptus.compute.common.PrivateIpAddressesSetItemRequestType;
import com.eucalyptus.compute.common.PrivateIpAddressesSetRequestType;
import com.eucalyptus.compute.common.SecurityGroupIdSetItemType;
import com.eucalyptus.compute.common.SecurityGroupIdSetType;
import com.eucalyptus.compute.common.SubnetIdSetItemType;
import com.eucalyptus.compute.common.SubnetIdSetType;
import com.eucalyptus.compute.common.TagInfo;
import com.eucalyptus.compute.common.UnassignPrivateIpAddressesType;
import com.eucalyptus.configurable.ConfigurableField;
import com.eucalyptus.util.async.AsyncRequests;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Created by ethomas on 2/3/14.
*/
public class AWSEC2NetworkInterfaceResourceAction extends StepBasedResourceAction {
private AWSEC2NetworkInterfaceProperties properties = new AWSEC2NetworkInterfaceProperties();
private AWSEC2NetworkInterfaceResourceInfo info = new AWSEC2NetworkInterfaceResourceInfo();
@ConfigurableField(initial = "300", description = "The amount of time (in seconds) to wait for a network interface to be available after create)")
public static volatile Integer NETWORK_INTERFACE_AVAILABLE_MAX_CREATE_RETRY_SECS = 300;
@ConfigurableField(initial = "300", description = "The amount of time (in seconds) to wait for a network interface to be deleted)")
public static volatile Integer NETWORK_INTERFACE_DELETED_MAX_DELETE_RETRY_SECS = 300;
public AWSEC2NetworkInterfaceResourceAction() {
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;
AWSEC2NetworkInterfaceResourceAction otherAction = (AWSEC2NetworkInterfaceResourceAction) resourceAction;
if (!Objects.equals(properties.getDescription(), otherAction.properties.getDescription())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getGroupSet(), otherAction.properties.getGroupSet())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getPrivateIpAddress(), otherAction.properties.getPrivateIpAddress())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
if (!Objects.equals(properties.getPrivateIpAddresses(), otherAction.properties.getPrivateIpAddresses())) {
if (primaryAddressChanged(properties.getPrivateIpAddresses(), otherAction.properties.getPrivateIpAddresses())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
} else {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
}
if (!Objects.equals(properties.getSecondaryPrivateIpAddressCount(), otherAction.properties.getSecondaryPrivateIpAddressCount())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getSourceDestCheck(), otherAction.properties.getSourceDestCheck())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getSubnetId(), otherAction.properties.getSubnetId())) {
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 boolean primaryAddressChanged(List<PrivateIpAddressSpecification> privateIpAddresses1, List<PrivateIpAddressSpecification> privateIpAddresses2) throws ValidationErrorException {
String primaryAddress1 = getPrimaryAddressStr(privateIpAddresses1);
String primaryAddress2 = getPrimaryAddressStr(privateIpAddresses2);
return !Objects.equals(primaryAddress1, primaryAddress2);
}
private String getPrimaryAddressStr(List<PrivateIpAddressSpecification> privateIpAddresses) throws ValidationErrorException {
String primary = null;
if (privateIpAddresses != null) {
for (PrivateIpAddressSpecification spec: privateIpAddresses) {
if (spec != null && Boolean.TRUE.equals(spec.getPrimary())) {
if (primary != null) throw new ValidationErrorException("More than one primary private ip address was passed in");
primary = spec.getPrivateIpAddress();
}
}
}
return primary;
}
private enum CreateSteps implements Step {
CREATE_NETWORK_INTERFACE {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2NetworkInterfaceResourceAction action = (AWSEC2NetworkInterfaceResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
CreateNetworkInterfaceType createNetworkInterfaceType = MessageHelper.createMessage(CreateNetworkInterfaceType.class, action.info.getEffectiveUserId());
createNetworkInterfaceType.setSubnetId(action.properties.getSubnetId());
if (!Strings.isNullOrEmpty(action.properties.getDescription())) {
createNetworkInterfaceType.setDescription(action.properties.getDescription());
}
if (!Strings.isNullOrEmpty(action.properties.getPrivateIpAddress())) {
createNetworkInterfaceType.setPrivateIpAddress(action.properties.getPrivateIpAddress());
}
if (action.properties.getPrivateIpAddresses() != null && !action.properties.getPrivateIpAddresses().isEmpty()) {
if (action.properties.getSecondaryPrivateIpAddressCount() != null) {
throw new ValidationErrorException("SecondaryPrivateIpAddressCount can only be assigned when no secondary private IP address is specified in PrivateIpAddresses");
}
createNetworkInterfaceType.setPrivateIpAddressesSet(action.convertPrivateIpAddresses(action.properties.getPrivateIpAddresses()));
}
if (action.properties.getGroupSet() != null && !action.properties.getGroupSet().isEmpty()) {
createNetworkInterfaceType.setGroupSet(action.convertGroupSet(action.properties.getGroupSet()));
}
if (action.properties.getSecondaryPrivateIpAddressCount() != null) {
action.properties.setSecondaryPrivateIpAddressCount(action.properties.getSecondaryPrivateIpAddressCount());
}
CreateNetworkInterfaceResponseType createNetworkInterfaceResponseType = AsyncRequests.<CreateNetworkInterfaceType, CreateNetworkInterfaceResponseType>sendSync(configuration, createNetworkInterfaceType);
action.info.setPhysicalResourceId(createNetworkInterfaceResponseType.getNetworkInterface().getNetworkInterfaceId());
action.info.setCreatedEnoughToDelete(true);
action.info.setReferenceValueJson(JsonHelper.getStringFromJsonNode(new TextNode(action.info.getPhysicalResourceId())));
return action;
}
},
SET_SOURCE_DEST_CHECK {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2NetworkInterfaceResourceAction action = (AWSEC2NetworkInterfaceResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (action.properties.getSourceDestCheck() != null) {
ModifyNetworkInterfaceAttributeType modifyNetworkInterfaceAttributeType = MessageHelper.createMessage(ModifyNetworkInterfaceAttributeType.class, action.info.getEffectiveUserId());
modifyNetworkInterfaceAttributeType.setNetworkInterfaceId(action.info.getPhysicalResourceId());
modifyNetworkInterfaceAttributeType.setSourceDestCheck(action.convertAttributeBooleanValueType(action.properties.getSourceDestCheck()));
ModifyNetworkInterfaceAttributeResponseType modifyNetworkInterfaceAttributeResponseType = AsyncRequests.sendSync(configuration, modifyNetworkInterfaceAttributeType);
}
return action;
}
},
GET_PRIVATE_IP_ADDRESS {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2NetworkInterfaceResourceAction action = (AWSEC2NetworkInterfaceResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
// Note: this is done separately, because an exception is thrown if not exactly one item is primary and we won't persist the network interface id,
// but it will have been created
DescribeNetworkInterfacesType describeNetworkInterfacesType = MessageHelper.createMessage(DescribeNetworkInterfacesType.class, action.info.getEffectiveUserId());
describeNetworkInterfacesType.getFilterSet( ).add( Filter.filter( "network-interface-id", action.info.getPhysicalResourceId( ) ) );
DescribeNetworkInterfacesResponseType describeNetworkInterfacesResponseType = AsyncRequests.sendSync(configuration, describeNetworkInterfacesType);
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet() == null ||
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem() == null ||
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().size() != 1) {
throw new ValidationErrorException("Network interface " + action.info.getPhysicalResourceId() + " either does not exist or is not unique");
}
// Get the private ip addresses
String primaryIp = null;
boolean foundPrimary = false;
ArrayNode secondaryIpArrayNode = JsonHelper.createArrayNode();
for (NetworkInterfacePrivateIpAddressesSetItemType networkInterfacePrivateIpAddressesSetItemType : describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().get(0).getPrivateIpAddressesSet().getItem()) {
if (networkInterfacePrivateIpAddressesSetItemType.getPrimary()) {
if (foundPrimary) {
throw new ValidationErrorException("Network interface " + action.info.getPhysicalResourceId() + " has a non-unique primary private ip address");
} else {
primaryIp = networkInterfacePrivateIpAddressesSetItemType.getPrivateIpAddress();
foundPrimary = true;
}
} else {
secondaryIpArrayNode.add(networkInterfacePrivateIpAddressesSetItemType.getPrivateIpAddress());
}
}
if (!foundPrimary) {
throw new ValidationErrorException("Network interface " + action.info.getPhysicalResourceId() + " has no primary private ip address");
}
action.info.setPrimaryPrivateIpAddress(JsonHelper.getStringFromJsonNode(new TextNode(primaryIp)));
action.info.setSecondaryPrivateIpAddresses(JsonHelper.getStringFromJsonNode(secondaryIpArrayNode));
return action;
}
},
VERIFY_AVAILABLE {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2NetworkInterfaceResourceAction action = (AWSEC2NetworkInterfaceResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
DescribeNetworkInterfacesType describeNetworkInterfacesType = MessageHelper.createMessage(DescribeNetworkInterfacesType.class, action.info.getEffectiveUserId());
describeNetworkInterfacesType.getFilterSet( ).add( Filter.filter( "network-interface-id", action.info.getPhysicalResourceId( ) ) );
DescribeNetworkInterfacesResponseType describeNetworkInterfacesResponseType = AsyncRequests.sendSync( configuration, describeNetworkInterfacesType );
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().size() ==0) {
throw new RetryAfterConditionCheckFailedException("Network interface " + action.info.getPhysicalResourceId() + " not yet available");
}
if (!"available".equals(describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().get(0).getStatus())) {
throw new RetryAfterConditionCheckFailedException("Volume " + action.info.getPhysicalResourceId() + " not yet available");
}
return action;
}
@Override
public Integer getTimeout() {
return NETWORK_INTERFACE_AVAILABLE_MAX_CREATE_RETRY_SECS;
}
},
CREATE_TAGS {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2NetworkInterfaceResourceAction action = (AWSEC2NetworkInterfaceResourceAction) 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 {
DELETE_NETWORK_INTERFACE {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2NetworkInterfaceResourceAction action = (AWSEC2NetworkInterfaceResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return action;
if (checkDeleted(action, configuration)) return action;
DeleteNetworkInterfaceType deleteNetworkInterfaceType = MessageHelper.createMessage(DeleteNetworkInterfaceType.class, action.info.getEffectiveUserId());
deleteNetworkInterfaceType.setNetworkInterfaceId(action.info.getPhysicalResourceId());
AsyncRequests.<DeleteNetworkInterfaceType, DeleteNetworkInterfaceResponseType>sendSync(configuration, deleteNetworkInterfaceType);
return action;
}
},
VERIFY_DELETE {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2NetworkInterfaceResourceAction action = (AWSEC2NetworkInterfaceResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return action;
if (checkDeleted(action, configuration)) return action;
throw new RetryAfterConditionCheckFailedException("Network interface " + action.info.getPhysicalResourceId() + " not yet deleted");
}
@Override
public Integer getTimeout() {
return NETWORK_INTERFACE_DELETED_MAX_DELETE_RETRY_SECS;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
private static boolean checkDeleted(AWSEC2NetworkInterfaceResourceAction action, ServiceConfiguration configuration) throws Exception {
// check if network interface still exists (return otherwise)
DescribeNetworkInterfacesType describeNetworkInterfacesType = MessageHelper.createMessage(DescribeNetworkInterfacesType.class, action.info.getEffectiveUserId());
describeNetworkInterfacesType.getFilterSet( ).add( Filter.filter( "network-interface-id", action.info.getPhysicalResourceId( ) ) );
DescribeNetworkInterfacesResponseType describeNetworkInterfacesResponseType = AsyncRequests.sendSync(configuration, describeNetworkInterfacesType);
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet() == null ||
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem() == null ||
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().isEmpty()) {
return true;
}
return false;
}
}
private enum UpdateNoInterruptionSteps implements UpdateStep {
UPDATE_NETWORK_INTERFACE_ATTRIBUTES {
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2NetworkInterfaceResourceAction oldAction = (AWSEC2NetworkInterfaceResourceAction) oldResourceAction;
AWSEC2NetworkInterfaceResourceAction newAction = (AWSEC2NetworkInterfaceResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (!Objects.equals(oldAction.properties.getDescription(), newAction.properties.getDescription()) ||
!Objects.equals(oldAction.properties.getGroupSet(), newAction.properties.getGroupSet()) ||
!Objects.equals(oldAction.properties.getSourceDestCheck(), newAction.properties.getSourceDestCheck())) {
ModifyNetworkInterfaceAttributeType modifyNetworkInterfaceAttributeType = MessageHelper.createMessage(ModifyNetworkInterfaceAttributeType.class, newAction.info.getEffectiveUserId());
modifyNetworkInterfaceAttributeType.setNetworkInterfaceId(newAction.info.getPhysicalResourceId());
if (!Objects.equals(oldAction.properties.getDescription(), newAction.properties.getDescription())) {
modifyNetworkInterfaceAttributeType.setDescription(newAction.convertNullableAttributeValueType(newAction.properties.getDescription() != null ? newAction.properties.getDescription() : ""));
}
if (!Objects.equals(oldAction.properties.getGroupSet(), newAction.properties.getGroupSet())) {
modifyNetworkInterfaceAttributeType.setGroupSet(newAction.convertGroupSet(newAction.properties.getGroupSet() != null && !newAction.properties.getGroupSet().isEmpty() ?
newAction.properties.getGroupSet() : Lists.newArrayList(newAction.getDefaultGroupId(configuration) )));
}
if (!Objects.equals(oldAction.properties.getSourceDestCheck(), newAction.properties.getSourceDestCheck())) {
modifyNetworkInterfaceAttributeType.setSourceDestCheck(newAction.convertAttributeBooleanValueType(newAction.properties.getSourceDestCheck() != null ? newAction.properties.getSourceDestCheck() : Boolean.TRUE));
}
ModifyNetworkInterfaceAttributeResponseType modifyNetworkInterfaceAttributeResponseType = AsyncRequests.sendSync(configuration, modifyNetworkInterfaceAttributeType);
}
return newAction;
}
},
UPDATE_SECONDARY_IP_ADDRESSES {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2NetworkInterfaceResourceAction oldAction = (AWSEC2NetworkInterfaceResourceAction) oldResourceAction;
AWSEC2NetworkInterfaceResourceAction newAction = (AWSEC2NetworkInterfaceResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (newAction.properties.getPrivateIpAddresses() != null && !newAction.properties.getPrivateIpAddresses().isEmpty()
&& newAction.properties.getSecondaryPrivateIpAddressCount() != null) {
throw new ValidationErrorException("SecondaryPrivateIpAddressCount can only be assigned when no secondary private IP address is specified in PrivateIpAddresses");
}
if (!Objects.equals(oldAction.properties.getPrivateIpAddresses(), newAction.properties.getPrivateIpAddresses()) ||
!Objects.equals(oldAction.properties.getSecondaryPrivateIpAddressCount(), newAction.properties.getSecondaryPrivateIpAddressCount())) {
// Based on tests at aws, if the previous stack has a secondary private ip address count, all old values should be removed. (Even ones possibly added externally)
// Otherwise, just the literal secondary values should be removed.
// So describe addresses and remove
// 1) All secondary addresses if the oldAction had a secondary address count
// 2) all the secondary addresses that are in the old list, that still are attached, and not in the new list (if there is a new list) otherwise
Set<String> oldSecondaryAddresses = oldAction.convertToSetOfAddressStrings(oldAction.properties.getPrivateIpAddresses());
Set<String> newSecondaryAddresses = newAction.convertToSetOfAddressStrings(newAction.properties.getPrivateIpAddresses());
Set<String> existingSecondaryAddresses = Sets.newLinkedHashSet();
DescribeNetworkInterfacesType describeNetworkInterfacesType = MessageHelper.createMessage(DescribeNetworkInterfacesType.class, newAction.info.getEffectiveUserId());
describeNetworkInterfacesType.getFilterSet( ).add( Filter.filter( "network-interface-id", newAction.info.getPhysicalResourceId( ) ) );
DescribeNetworkInterfacesResponseType describeNetworkInterfacesResponseType = AsyncRequests.sendSync(configuration, describeNetworkInterfacesType);
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet() == null ||
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem() == null ||
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().size() != 1) {
throw new ValidationErrorException("Network interface " + newAction.info.getPhysicalResourceId() + " either does not exist or is not unique");
}
for (NetworkInterfacePrivateIpAddressesSetItemType networkInterfacePrivateIpAddressesSetItemType : describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().get(0).getPrivateIpAddressesSet().getItem()) {
if (!Boolean.TRUE.equals(networkInterfacePrivateIpAddressesSetItemType.getPrimary())) {
existingSecondaryAddresses.add(networkInterfacePrivateIpAddressesSetItemType.getPrivateIpAddress());
}
}
Set<String> addressesToRemove = Sets.newLinkedHashSet();
if (oldAction.properties.getSecondaryPrivateIpAddressCount() != null && oldAction.properties.getSecondaryPrivateIpAddressCount() > 0) {
addressesToRemove.addAll(existingSecondaryAddresses);
} else {
addressesToRemove.addAll(Sets.difference(Sets.intersection(existingSecondaryAddresses, oldSecondaryAddresses), newSecondaryAddresses));
}
if (!addressesToRemove.isEmpty()) {
UnassignPrivateIpAddressesType unassignPrivateIpAddressesType = MessageHelper.createMessage(UnassignPrivateIpAddressesType.class, newAction.info.getEffectiveUserId());
unassignPrivateIpAddressesType.setPrivateIpAddressesSet(newAction.convertToPrivateIpAddressSet(addressesToRemove));
unassignPrivateIpAddressesType.setNetworkInterfaceId(newAction.info.getPhysicalResourceId());
AsyncRequests.sendSync(configuration, unassignPrivateIpAddressesType);
}
// once here, if we have any addresses or count, add them as appropriate
if (!newSecondaryAddresses.isEmpty() ||
(newAction.properties.getSecondaryPrivateIpAddressCount() != null && newAction.properties.getSecondaryPrivateIpAddressCount() > 0)) {
AssignPrivateIpAddressesType assignPrivateIpAddressesType = MessageHelper.createMessage(AssignPrivateIpAddressesType.class, newAction.info.getEffectiveUserId());
assignPrivateIpAddressesType.setNetworkInterfaceId(newAction.info.getPhysicalResourceId());
if (newAction.properties.getSecondaryPrivateIpAddressCount() != null && newAction.properties.getSecondaryPrivateIpAddressCount() > 0) {
assignPrivateIpAddressesType.setSecondaryPrivateIpAddressCount(newAction.properties.getSecondaryPrivateIpAddressCount());
} else {
assignPrivateIpAddressesType.setPrivateIpAddressesSet(newAction.convertToPrivateIpAddressSet(Sets.difference(newSecondaryAddresses, Sets.intersection(oldSecondaryAddresses, existingSecondaryAddresses))));
}
AsyncRequests.sendSync(configuration, assignPrivateIpAddressesType);
}
}
return newAction;
}
},
UPDATE_IP_ADDRESS_ATTRIBUTES {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2NetworkInterfaceResourceAction oldAction = (AWSEC2NetworkInterfaceResourceAction) oldResourceAction;
AWSEC2NetworkInterfaceResourceAction newAction = (AWSEC2NetworkInterfaceResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
// Note: this is done separately, because an exception is thrown if not exactly one item is primary and we won't persist the network interface id,
// but it will have been created
DescribeNetworkInterfacesType describeNetworkInterfacesType = MessageHelper.createMessage(DescribeNetworkInterfacesType.class, newAction.info.getEffectiveUserId());
describeNetworkInterfacesType.getFilterSet( ).add( Filter.filter("network-interface-id", newAction.info.getPhysicalResourceId()) );
DescribeNetworkInterfacesResponseType describeNetworkInterfacesResponseType = AsyncRequests.sendSync(configuration, describeNetworkInterfacesType);
if (describeNetworkInterfacesResponseType.getNetworkInterfaceSet() == null ||
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem() == null ||
describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().size() != 1) {
throw new ValidationErrorException("Network interface " + newAction.info.getPhysicalResourceId() + " either does not exist or is not unique");
}
// Get the private ip addresses
String primaryIp = null;
boolean foundPrimary = false;
ArrayNode secondaryIpArrayNode = JsonHelper.createArrayNode();
for (NetworkInterfacePrivateIpAddressesSetItemType networkInterfacePrivateIpAddressesSetItemType : describeNetworkInterfacesResponseType.getNetworkInterfaceSet().getItem().get(0).getPrivateIpAddressesSet().getItem()) {
if (networkInterfacePrivateIpAddressesSetItemType.getPrimary()) {
if (foundPrimary) {
throw new ValidationErrorException("Network interface " + newAction.info.getPhysicalResourceId() + " has a non-unique primary private ip address");
} else {
primaryIp = networkInterfacePrivateIpAddressesSetItemType.getPrivateIpAddress();
foundPrimary = true;
}
} else {
secondaryIpArrayNode.add(networkInterfacePrivateIpAddressesSetItemType.getPrivateIpAddress());
}
}
if (!foundPrimary) {
throw new ValidationErrorException("Network interface " + newAction.info.getPhysicalResourceId() + " has no primary private ip address");
}
newAction.info.setPrimaryPrivateIpAddress(JsonHelper.getStringFromJsonNode(new TextNode(primaryIp)));
newAction.info.setSecondaryPrivateIpAddresses(JsonHelper.getStringFromJsonNode(secondaryIpArrayNode));
return newAction;
}
},
UPDATE_TAGS {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSEC2NetworkInterfaceResourceAction oldAction = (AWSEC2NetworkInterfaceResourceAction) oldResourceAction;
AWSEC2NetworkInterfaceResourceAction newAction = (AWSEC2NetworkInterfaceResourceAction) 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;
}
}
private String getDefaultGroupId(ServiceConfiguration configuration) throws Exception {
DescribeSubnetsType describeSubnetsType = MessageHelper.createMessage(DescribeSubnetsType.class, info.getEffectiveUserId());
describeSubnetsType.setSubnetSet(makeSubnetSet(properties.getSubnetId()));
DescribeSubnetsResponseType describeSubnetsResponseType = AsyncRequests.sendSync(configuration, describeSubnetsType);
if (describeSubnetsResponseType == null || describeSubnetsResponseType.getSubnetSet() == null || describeSubnetsResponseType.getSubnetSet().getItem() == null ||
describeSubnetsResponseType.getSubnetSet().getItem().size() != 1) {
throw new ValidationErrorException("Subnet id " + properties.getSubnetId() + " matches either zero or more than one subnet");
}
String vpcId = describeSubnetsResponseType.getSubnetSet().getItem().get(0).getVpcId();
DescribeSecurityGroupsType describeSecurityGroupsType = MessageHelper.createMessage(DescribeSecurityGroupsType.class, info.getEffectiveUserId());
describeSecurityGroupsType.getFilterSet().add(Filter.filter("vpc-id", vpcId));
describeSecurityGroupsType.getFilterSet().add(Filter.filter("group-name", "default"));
DescribeSecurityGroupsResponseType describeSecurityGroupsResponseType = AsyncRequests.sendSync(configuration, describeSecurityGroupsType);
if (describeSecurityGroupsResponseType == null || describeSecurityGroupsResponseType.getSecurityGroupInfo() == null ||
describeSecurityGroupsResponseType.getSecurityGroupInfo().size() != 1) {
throw new ValidationErrorException("Could not find unique default security group for vpc " + vpcId);
}
return describeSecurityGroupsResponseType.getSecurityGroupInfo().get(0).getGroupId();
}
private SubnetIdSetType makeSubnetSet(String subnetId) {
SubnetIdSetType subnetIdSetType = new SubnetIdSetType();
ArrayList<SubnetIdSetItemType> item = Lists.newArrayList();
SubnetIdSetItemType subnetIdSetItemType = new SubnetIdSetItemType();
subnetIdSetItemType.setSubnetId(subnetId);
item.add(subnetIdSetItemType);
subnetIdSetType.setItem(item);
return subnetIdSetType;
}
private NullableAttributeValueType convertNullableAttributeValueType(String value) {
NullableAttributeValueType nullableAttributeValueType = new NullableAttributeValueType();
nullableAttributeValueType.setValue(value);
return nullableAttributeValueType;
}
private AssignPrivateIpAddressesSetRequestType convertToPrivateIpAddressSet(Set<String> ipAddresses) {
AssignPrivateIpAddressesSetRequestType assignPrivateIpAddressesSetRequestType = new AssignPrivateIpAddressesSetRequestType();
ArrayList<AssignPrivateIpAddressesSetItemRequestType> item = Lists.newArrayList();
if (ipAddresses != null) {
for (String ipAddress: ipAddresses) {
AssignPrivateIpAddressesSetItemRequestType assignPrivateIpAddressesSetItemRequestType = new AssignPrivateIpAddressesSetItemRequestType();
assignPrivateIpAddressesSetItemRequestType.setPrivateIpAddress(ipAddress);
item.add(assignPrivateIpAddressesSetItemRequestType);
}
}
assignPrivateIpAddressesSetRequestType.setItem(item);
return assignPrivateIpAddressesSetRequestType;
}
private Set<String> convertToSetOfAddressStrings(List<PrivateIpAddressSpecification> privateIpAddresses) {
Set<String> setOfAddressStrings = Sets.newLinkedHashSet();
if (privateIpAddresses != null) {
for (PrivateIpAddressSpecification privateIpAddressSpecification: privateIpAddresses) {
if (privateIpAddressSpecification != null && !Boolean.TRUE.equals(privateIpAddressSpecification.getPrimary()) && privateIpAddressSpecification.getPrivateIpAddress() != null) {
setOfAddressStrings.add(privateIpAddressSpecification.getPrivateIpAddress());
}
}
}
return setOfAddressStrings;
}
@Override
public ResourceProperties getResourceProperties() {
return properties;
}
@Override
public void setResourceProperties(ResourceProperties resourceProperties) {
properties = (AWSEC2NetworkInterfaceProperties) resourceProperties;
}
@Override
public ResourceInfo getResourceInfo() {
return info;
}
@Override
public void setResourceInfo(ResourceInfo resourceInfo) {
info = (AWSEC2NetworkInterfaceResourceInfo) resourceInfo;
}
private SecurityGroupIdSetType convertGroupSet(List<String> groupSet) {
SecurityGroupIdSetType securityGroupIdSetType = new SecurityGroupIdSetType();
ArrayList<SecurityGroupIdSetItemType> item = Lists.newArrayList();
for (String groupId: groupSet) {
SecurityGroupIdSetItemType securityGroupIdSetItemType = new SecurityGroupIdSetItemType();
securityGroupIdSetItemType.setGroupId(groupId);
item.add(securityGroupIdSetItemType);
}
securityGroupIdSetType.setItem(item);
return securityGroupIdSetType;
}
private PrivateIpAddressesSetRequestType convertPrivateIpAddresses(List<PrivateIpAddressSpecification> privateIpAddresses) {
PrivateIpAddressesSetRequestType privateIpAddressesSetRequestType = new PrivateIpAddressesSetRequestType();
ArrayList<PrivateIpAddressesSetItemRequestType> item = Lists.newArrayList();
for (PrivateIpAddressSpecification privateIpAddressSpecification: privateIpAddresses) {
PrivateIpAddressesSetItemRequestType privateIpAddressesSetItemRequestType = new PrivateIpAddressesSetItemRequestType();
privateIpAddressesSetItemRequestType.setPrivateIpAddress(privateIpAddressSpecification.getPrivateIpAddress());
privateIpAddressesSetItemRequestType.setPrimary(privateIpAddressSpecification.getPrimary());
item.add(privateIpAddressesSetItemRequestType);
}
privateIpAddressesSetRequestType.setItem(item);
return privateIpAddressesSetRequestType;
}
private AttributeBooleanValueType convertAttributeBooleanValueType(Boolean bool) {
AttributeBooleanValueType attributeBooleanValueType = new AttributeBooleanValueType();
attributeBooleanValueType.setValue(bool);
return attributeBooleanValueType;
}
}