/*************************************************************************
* 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.compute.common.internal.address;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Index;
import javax.persistence.PersistenceContext;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import com.eucalyptus.auth.principal.FullName;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.eucalyptus.auth.principal.Principals;
import com.eucalyptus.compute.common.CloudMetadata;
import com.eucalyptus.compute.common.Compute;
import com.eucalyptus.compute.common.internal.vm.VmInstance;
import com.eucalyptus.compute.common.internal.vm.VmInstances;
import com.eucalyptus.compute.common.internal.vm.VmNetworkConfig;
import com.eucalyptus.compute.common.internal.vpc.NetworkInterface;
import com.eucalyptus.entities.AccountMetadata;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.entities.UserMetadata;
import com.eucalyptus.upgrade.Upgrades;
import com.eucalyptus.util.HasFullName;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
import groovy.sql.Sql;
import org.apache.log4j.Logger;
/**
* Entity representing an in-use address
*/
@Entity
@PersistenceContext( name = "eucalyptus_cloud" )
@Table( name = "metadata_addresses", indexes = {
@Index( name = "metadata_addresses_user_id_idx", columnList = "metadata_user_id" ),
@Index( name = "metadata_addresses_account_id_idx", columnList = "metadata_account_id" ),
@Index( name = "metadata_addresses_display_name_idx", columnList = "metadata_display_name" ),
} )
public class AllocatedAddressEntity extends UserMetadata<AddressState> implements AddressI, CloudMetadata.AddressMetadata {
private static final long serialVersionUID = 1L;
/**
* EC2 VPC domain. Null unless allocated for use in VPC.
*/
@Enumerated( EnumType.STRING )
@Column( name = "metadata_domain" )
private AddressDomain domain;
/**
* The instance ID that the address is assigned to (Classic) of that the addresses
* associated ENI is attached to.
*/
@Column( name = "metadata_instance_id" )
private String instanceId;
/**
* The instance uuid that the address is assigned to (Classic) of that the addresses
* associated ENI is attached to.
*/
@Column( name = "metadata_instance_uuid" )
private String instanceUuid;
/**
* EC2 VPC allocation identifier. Null unless allocated for use in VPC.
*/
@Column( name = "metadata_allocation_id" )
private String allocationId;
/**
* EC2 VPC association identifier. Null unless associated and allocated for use in VPC.
*/
@Column( name = "metadata_association_id" )
private String associationId;
/**
* EC2 VPC network interface identifier. Null unless associated and allocated for use in VPC.
*/
@Column( name = "metadata_association_eni_id" )
private String networkInterfaceId;
/**
* EC2 VPC network interface owner identifier. Null unless associated and allocated for use in VPC.
*/
@Column( name = "metadata_association_eni_owner_id" )
private String networkInterfaceOwnerId;
/**
* EC2 VPC private address. Null unless associated and allocated for use in VPC.
*/
@Column( name = "metadata_association_private_address" )
private String privateAddress;
protected AllocatedAddressEntity() {}
protected AllocatedAddressEntity( String ipAddress ) {
this( Principals.nobodyFullName( ), ipAddress );
}
protected AllocatedAddressEntity( final OwnerFullName owner, final String ipAddress ) {
super( owner, ipAddress );
}
public static AllocatedAddressEntity create( ) {
return new AllocatedAddressEntity( );
}
public static AllocatedAddressEntity exampleWithAddress(
@Nullable final String ip
) {
return exampleWithOwnerAndAddress( null, ip );
}
public static AllocatedAddressEntity exampleWithOwnerAndAddress(
@Nullable final OwnerFullName owner,
@Nullable final String ip
) {
return new AllocatedAddressEntity( owner, ip );
}
public static AllocatedAddressEntity exampleWithNaturalId(
final String uuid
) {
final AllocatedAddressEntity example = new AllocatedAddressEntity( );
example.setNaturalId( uuid );
return example;
}
public static AllocatedAddressEntity example() {
return new AllocatedAddressEntity(null, null);
}
public String getAddress( ) {
return getDisplayName( );
}
public boolean isAllocated( ) {
return this.getState( ).ordinal( ) > AddressState.unallocated.ordinal( );
}
public boolean isSystemOwned( ) {
return Principals.systemFullName( ).equals( this.getOwner( ) );
}
/**
* Is the instance assigned or with an impending assignment.
*
* <P>WARNING! in this state the instance ID may not be a valid instance
* identifier.</P>
*
* @return True if assigned or assignment impending.
*/
public boolean isAssigned( ) {
return this.getState( ).ordinal( ) > AddressState.allocated.ordinal( );
}
public boolean isStarted( ) {
return this.getState( ).ordinal( ) > AddressState.assigned.ordinal( );
}
public String getUserId( ) {
return this.getOwner( ).getUserId( );
}
/**
* Some address states
*/
@Nonnull
public AddressDomain domainWithDefault( ) {
return Objects.firstNonNull( getDomain( ), AddressDomain.standard );
}
@Nullable
public AddressDomain getDomain( ) {
return domain;
}
public void setDomain( final AddressDomain domain ) {
this.domain = domain;
}
public String getInstanceId( ) {
return instanceId;
}
public void setInstanceId( final String instanceId ) {
this.instanceId = instanceId;
}
public String getInstanceUuid( ) {
return instanceUuid;
}
public void setInstanceUuid( final String instanceUuid ) {
this.instanceUuid = instanceUuid;
}
public String getAllocationId( ) {
return allocationId;
}
public void setAllocationId( final String allocationId ) {
this.allocationId = allocationId;
}
public String getAssociationId( ) {
return associationId;
}
public void setAssociationId( final String associationId ) {
this.associationId = associationId;
}
public String getNetworkInterfaceId( ) {
return networkInterfaceId;
}
public void setNetworkInterfaceId( final String networkInterfaceId ) {
this.networkInterfaceId = networkInterfaceId;
}
public String getNetworkInterfaceOwnerId( ) {
return networkInterfaceOwnerId;
}
public void setNetworkInterfaceOwnerId( final String networkInterfaceOwnerId ) {
this.networkInterfaceOwnerId = networkInterfaceOwnerId;
}
public String getPrivateAddress( ) {
return privateAddress;
}
public void setPrivateAddress( final String privateAddress ) {
this.privateAddress = privateAddress;
}
@Override
public void setOwnerAccountNumber( final String ownerAccountId ) {
super.setOwnerAccountNumber( ownerAccountId );
}
@Override
public String toString( ) {
return "Address " + this.getDisplayName( ) + " " + ( this.isAllocated( )
? this.getOwner( ) + " "
: "" );
}
@Override
public boolean equals( final Object o ) {
if ( this == o ) return true;
if ( !( o instanceof AllocatedAddressEntity ) ) return false;
AllocatedAddressEntity address = ( AllocatedAddressEntity ) o;
if ( !this.getDisplayName( ).equals( address.getDisplayName( ) ) ) return false;
return true;
}
@Override
public int hashCode( ) {
return this.getDisplayName( ).hashCode( );
}
@Override
public FullName getFullName( ) {
return FullName.create.vendor( "euca" ).region( "cluster" ).namespace( this.getPartition( ) ).relativeId( "public-address",
this.getName( ) );
}
@Override
public int compareTo( AccountMetadata that ) {
if ( that instanceof AllocatedAddressEntity ) {
return this.getName( ).compareTo( that.getName( ) );
} else {
return super.compareTo( that );
}
}
/**
* @see HasFullName#getPartition()
*/
@Override
public String getPartition( ) {
return "eucalyptus";
}
@Override
protected String createUniqueName( ) {
return getDisplayName( );
}
@PrePersist
@PreUpdate
private void checkState( ) {
if ( getState( ).ordinal( ) < AddressState.allocated.ordinal( ) ) {
throw new IllegalStateException( "Address " + getDisplayName( ) + " state not valid for persistence: " + getState( ) );
}
}
public static Function<AddressI,String> allocation( ) {
return FilterFunctions.ALLOCATION_ID;
}
public enum FilterFunctions implements Function<AddressI,String> {
ALLOCATION_ID {
@Nullable
@Override
public String apply( final AddressI address ) {
return address.getAllocationId( );
}
},
ASSOCIATION_ID {
@Override
public String apply( final AddressI address ) {
return address.getAssociationId( );
}
},
DOMAIN {
@Override
public String apply( final AddressI address ) {
return address.getDomain( ) == null ?
AddressDomain.standard.toString( ) :
address.getDomain( ).toString( );
}
},
INSTANCE_ID {
@Override
public String apply( final AddressI address ) {
return address.getInstanceId( );
}
},
NETWORK_INTERFACE_ID {
@Override
public String apply( final AddressI address ) {
return address.getNetworkInterfaceId( );
}
},
NETWORK_INTERFACE_OWNER_ID {
@Override
public String apply( final AddressI address ) {
return address.getNetworkInterfaceOwnerId( );
}
},
PRIVATE_IP_ADDRESS {
@Override
public String apply( final AddressI address ) {
return address.getPrivateAddress( );
}
},
PUBLIC_IP {
@Override
public String apply( final AddressI address ) {
return address.getAddress( );
}
},
}
@Upgrades.PreUpgrade( since = Upgrades.Version.v4_2_0, value = Compute.class )
public static class AllocatedAddressEntity420PreUpgrade implements Callable<Boolean> {
private static final Logger logger = Logger.getLogger( AllocatedAddressEntity420PreUpgrade.class );
@Override
public Boolean call( ) throws Exception {
logger.info( "Cleaning up public addresses for instances" );
Sql sql = null;
try {
sql = Upgrades.DatabaseFilters.NEWVERSION.getConnection( "eucalyptus_cloud" );
int updated = sql.executeUpdate(
"update metadata_instances set metadata_vm_public_address = '0.0.0.0', version = version + 1 where " +
"metadata_vm_public_address != '0.0.0.0' and ( metadata_vm_public_address = '' or " +
"( metadata_vpc_id is null and metadata_state = 'STOPPED' ) )"
);
logger.info( "Cleared public address for " + updated + " instances" );
} catch ( final Exception e ) {
logger.error( "Error cleaning up public addresses for instances", e );
} finally {
if ( sql != null ) {
sql.close( );
}
}
return Boolean.TRUE;
}
}
@Upgrades.EntityUpgrade( entities = AllocatedAddressEntity.class, since = Upgrades.Version.v4_2_0, value = Compute.class )
public enum AllocatedAddressEntity420Upgrade implements Predicate<Class> {
INSTANCE;
@Override
public boolean apply( final Class entityClass ) {
final List<VmInstance> vmInstances = VmInstances.list( Predicates.alwaysTrue( ) );
try ( final TransactionResource tx = Entities.transactionFor( AllocatedAddressEntity.class ) ) {
// Populate Elastic IP info
final Set<String> addresses = Sets.newHashSetWithExpectedSize( 128 );
for ( final AllocatedAddressEntity entity : Entities.query( AllocatedAddressEntity.exampleWithAddress( null ) ) ) {
addresses.add( entity.getAddress( ) );
try {
final VmInstance instance = VmInstances.lookupByPublicIp( entity.getAddress( ) );
if ( instance.getVpcId( ) == null ) {
entity.setState( AddressState.assigned );
entity.setDomain( AddressDomain.standard );
entity.setInstanceId( instance.getDisplayName( ) );
entity.setInstanceUuid( instance.getInstanceUuid( ) );
entity.setPrivateAddress( instance.getPrivateAddress( ) );
} else if ( instance.getNetworkInterfaces( ) != null ) {
for ( final NetworkInterface networkInterface : instance.getNetworkInterfaces( ) ) {
if ( networkInterface.isAssociated( ) ) {
entity.setState( AddressState.assigned );
entity.setDomain( AddressDomain.vpc );
entity.setAllocationId( networkInterface.getAssociation( ).getAllocationId( ) );
entity.setAssociationId( networkInterface.getAssociation( ).getAssociationId( ) );
entity.setNetworkInterfaceId( networkInterface.getDisplayName( ) );
entity.setNetworkInterfaceOwnerId( networkInterface.getOwnerUserId( ) );
entity.setPrivateAddress( networkInterface.getPrivateIpAddress( ) );
if ( networkInterface.isAttached( ) && networkInterface.getAttachment( ).getDeviceIndex( ) == 0 ) {
entity.setInstanceId( instance.getDisplayName( ) );
entity.setInstanceUuid( instance.getInstanceUuid( ) );
}
}
}
}
} catch ( NoSuchElementException e ) {
entity.setState( AddressState.allocated );
if ( entity.getDomain( ) == null ) {
entity.setDomain( AddressDomain.standard );
}
}
}
// Add missing public IPs for instances
for ( final VmInstance instance : vmInstances ) {
if ( !VmNetworkConfig.DEFAULT_IP.equals( instance.getPublicAddress( ) ) && !addresses.contains( instance.getPublicAddress( ) ) ) {
if ( instance.getVpcId( ) == null ) {
final AllocatedAddressEntity entity = AllocatedAddressEntity.create( );
entity.setDisplayName( instance.getPublicAddress( ) );
entity.setState( AddressState.assigned );
entity.setOwner( Principals.systemFullName( ) );
entity.setInstanceId( instance.getDisplayName( ) );
entity.setInstanceUuid( instance.getInstanceUuid( ) );
entity.setPrivateAddress( instance.getPrivateAddress( ) );
Entities.persist( entity );
} else if ( instance.getNetworkInterfaces( ) != null ) {
for ( final NetworkInterface networkInterface : instance.getNetworkInterfaces( ) ) {
if ( networkInterface.isAssociated( ) ) {
final AllocatedAddressEntity entity = AllocatedAddressEntity.create( );
entity.setDisplayName( networkInterface.getAssociation( ).getPublicIp( ) );
entity.setState( AddressState.assigned );
entity.setOwner( Principals.systemFullName( ) );
entity.setAllocationId( networkInterface.getAssociation( ).getAllocationId( ) );
entity.setAssociationId( networkInterface.getAssociation( ).getAssociationId( ) );
entity.setNetworkInterfaceId( networkInterface.getDisplayName( ) );
entity.setNetworkInterfaceOwnerId( networkInterface.getOwnerUserId( ) );
entity.setPrivateAddress( networkInterface.getPrivateIpAddress( ) );
if ( networkInterface.isAttached( ) && networkInterface.getAttachment( ).getDeviceIndex( ) == 0 ) {
entity.setInstanceId( instance.getDisplayName( ) );
entity.setInstanceUuid( instance.getInstanceUuid( ) );
}
Entities.persist( entity );
}
}
}
}
}
tx.commit() ;
}
return true;
}
}
}