/*************************************************************************
* 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.
*
* This file may incorporate work covered under the following copyright
* and permission notice:
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Regents of the University of California
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms,
* with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
* THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
* COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
* AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
* IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
* SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
* WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
* REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
* IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
* NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
************************************************************************/
package com.eucalyptus.address;
import static com.eucalyptus.compute.common.internal.address.AllocatedAddressEntity.FilterFunctions.*;
import static com.eucalyptus.reporting.event.ResourceAvailabilityEvent.ResourceType;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.eucalyptus.address.Address.AddressInfo;
import com.eucalyptus.address.Address.AddressStateTransition;
import com.eucalyptus.auth.Accounts;
import com.eucalyptus.auth.principal.Principals;
import com.eucalyptus.auth.principal.UserFullName;
import com.eucalyptus.compute.common.AddressInfoType;
import com.eucalyptus.compute.common.Compute;
import com.eucalyptus.compute.common.internal.address.AddressDomain;
import com.eucalyptus.compute.common.internal.address.AddressI;
import com.eucalyptus.compute.common.internal.address.AddressState;
import com.eucalyptus.compute.common.internal.address.AllocatedAddressEntity;
import com.eucalyptus.compute.common.internal.vm.VmNetworkConfig;
import com.eucalyptus.compute.common.internal.vpc.NetworkInterface;
import com.eucalyptus.entities.PersistenceExceptions;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.network.NetworkInfoBroadcaster;
import com.eucalyptus.reporting.event.AddressEvent;
import com.eucalyptus.reporting.event.EventActionInfo;
import com.eucalyptus.system.Threads;
import com.eucalyptus.util.Callback;
import com.eucalyptus.util.FUtils;
import com.eucalyptus.util.RestrictedTypes;
import com.google.common.base.Predicates;
import com.google.common.base.Suppliers;
import org.apache.log4j.Logger;
import com.eucalyptus.bootstrap.Bootstrap;
import com.eucalyptus.bootstrap.Hosts;
import com.eucalyptus.compute.common.CloudMetadata.AddressMetadata;
import com.eucalyptus.compute.common.internal.util.NotEnoughResourcesException;
import com.eucalyptus.context.Context;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.event.ClockTick;
import com.eucalyptus.event.EventListener;
import com.eucalyptus.event.ListenerRegistry;
import com.eucalyptus.event.Listeners;
import com.eucalyptus.records.Logs;
import com.eucalyptus.reporting.event.ResourceAvailabilityEvent;
import com.eucalyptus.compute.common.internal.tags.FilterSupport;
import com.eucalyptus.util.CollectionUtils;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.LogUtil;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.eucalyptus.util.RestrictedTypes.QuantityMetricFunction;
import com.eucalyptus.util.RestrictedTypes.Resolver;
import com.eucalyptus.util.Strings;
import com.eucalyptus.util.TypeMapper;
import com.eucalyptus.compute.common.internal.vm.VmInstance;
import com.eucalyptus.compute.common.internal.vm.VmInstance.VmState;
import com.eucalyptus.vm.VmInstances;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import groovy.lang.Closure;
public class Addresses {
private static final String ERR_SYS_INSUFFICIENT_ADDRESS_CAPACITY = "InsufficientAddressCapacity";
private static final Logger LOG = Logger.getLogger( Addresses.class );
private static final AtomicReference<Iterable<String>> configuredAddresses = new AtomicReference<>( );
private static final Addresses instance = new Addresses( new AllocatedAddressPersistenceImpl( ) );
private static ThreadLocal<AddressingBatch> batchThreadLocal = new ThreadLocal<>( );
private final Supplier<Void> storedAddressLoadingSupplier = Suppliers.memoizeWithExpiration( new Supplier<Void>(){
@Override
public Void get( ) {
loadStoredAddresses( );
return null;
}
}, 1, TimeUnit.MINUTES );
private final AllocatedAddressPersistence allocatedAddressPersistence;
private final AddressRegistry addressRegistry = AddressRegistry.getInstance( );
public static Addresses getInstance( ) {
return instance;
}
public Addresses( final AllocatedAddressPersistence allocatedAddressPersistence ) {
this.allocatedAddressPersistence = allocatedAddressPersistence;
}
public List<Address> listActiveAddresses( ) {
return addressRegistry.listValues( );
}
public List<Address> listInactiveAddresses( ) {
return addressRegistry.listDisabledValues( );
}
public Address lookupActiveAddress( final String ip ) {
return addressRegistry.lookup( ip );
}
public Allocator allocator( final AddressDomain domain ) {
return new Allocator( this, domain );
}
public Address allocateNext( final UserFullName userId, final AddressDomain domain ) throws NotEnoughResourcesException {
final Predicate<Address> predicate = RestrictedTypes.filterPrivileged( );
final Address address;
try {
address = addressRegistry.enableFirst( predicate );
} catch ( final NoSuchElementException e ) {
throw new NotEnoughResourcesException( ERR_SYS_INSUFFICIENT_ADDRESS_CAPACITY );
}
final Optional<AddressStateTransition> transition = address.allocate( userId, domain );
if ( !transition.isPresent( ) ) {
throw new NotEnoughResourcesException( ERR_SYS_INSUFFICIENT_ADDRESS_CAPACITY );
}
try {
call( new Callable<Void>( ) {
@Override
public Void call( ) throws Exception {
allocatedAddressPersistence.save( createEntity( transition.get( ).newAddressInfo( ) ) );
return null;
}
} );
} catch ( final Exception e ) {
LOG.error( "Persistence error for allocated address, attempting rollback " + address.getDisplayName( ), e );
if ( transition.get( ).rollback( ) ) {
disable( address.getAddress( ) );
}
throw new NotEnoughResourcesException( ERR_SYS_INSUFFICIENT_ADDRESS_CAPACITY );
}
LOG.debug( "Allocated address for public addressing: " + String.valueOf( address ) );
fireUsageEvent( userId, address.getDisplayName( ), Suppliers.ofInstance( AddressEvent.forAllocate( ) ) );
return address;
}
public Address allocateSystemAddress( ) throws NotEnoughResourcesException {
return doAllocateSystemAddress( Optional.<String>absent( ) );
}
public Address allocateSystemAddress( final String address ) throws NotEnoughResourcesException {
return doAllocateSystemAddress( Optional.of( address ) );
}
public void assignSystemAddress( final VmInstance vm ) throws NotEnoughResourcesException {
doAssignSystemAddress( vm );
}
public boolean assign( final Address address, final VmInstance vm ) {
try ( final AddressingBatch batch = batch( ) ) {
boolean assigned = false;
if ( address.getOwnerAccountNumber( ) == null ||
vm.getOwnerAccountNumber( ).equals( address.getOwnerAccountNumber( ) ) ) {
boolean disableAddressOnFailure = false;
if ( !address.isAllocated( ) ) {
final Optional<Address> enabledAddress = addressRegistry.tryEnable( address.getAddress( ) );
disableAddressOnFailure = enabledAddress.isPresent( );
}
final Optional<AddressStateTransition> assignTransition = address.assign( vm );
if ( assignTransition.isPresent( ) ) {
assigned = store( address, assignTransition.get( ).newAddressInfo( ) ) || !assignTransition.get( ).rollback( );
}
if ( assigned && assignTransition.get( ).newAddressInfo( ).getOwnerUserId( ) != null ) {
fireAssociateUsageEvent( assignTransition.get( ) );
}
if ( !assigned && disableAddressOnFailure ) {
disable( address.getAddress( ) );
}
if ( assigned ) {
addressFlushRequired( );
}
}
return assigned;
}
}
/**
* EC2 classic unassign
*/
public boolean unassign( final String address ) {
boolean unassign = false;
try {
unassign = unassign( lookupActiveAddress( address ) );
} catch ( NoSuchElementException e ) {
// not unassigned
}
return unassign;
}
/**
* EC2 classic unassign
*/
public boolean unassign( final Address address ) {
try ( final AddressingBatch batch = batch( ) ) {
boolean unassign = false;
final Optional<AddressStateTransition> unassignTransition = address.unassign( );
if ( unassignTransition.isPresent( ) ) {
unassign = store( address, unassignTransition.get( ).newAddressInfo( ) ) || !unassignTransition.get( ).rollback( );
}
if ( unassign && unassignTransition.get( ).newAddressInfo( ).getOwnerUserId( ) != null ) {
fireDisassociateUsageEvent( unassignTransition.get( ) );
}
if ( unassign && !unassignTransition.get( ).newAddressInfo( ).isAllocated( ) ) {
disable( address.getAddress( ) );
}
if ( unassign ) {
addressFlushRequired( );
}
return unassign;
}
}
public boolean assign( final Address address, final NetworkInterface eni ) {
boolean assigned = false;
boolean disableAddressOnFailure = false;
if ( !address.isAllocated( ) ) {
final Optional<Address> enabledAddress = addressRegistry.tryEnable( address.getAddress( ) );
disableAddressOnFailure = enabledAddress.isPresent( );
}
final Optional<AddressStateTransition> assignTransition = address.assign( eni );
if ( assignTransition.isPresent( ) ) {
assigned = store( address, assignTransition.get( ).newAddressInfo( ) ) || !assignTransition.get( ).rollback( );
}
if ( !assigned && disableAddressOnFailure ) {
disable( address.getAddress( ) );
}
return assigned;
}
/**
* EC2 vpc unassign
*/
public boolean unassign( final Address address, final String associationId ) {
boolean unassign = false;
final Optional<AddressStateTransition> unassignTransition = address.unassign( associationId );
if ( unassignTransition.isPresent( ) ) {
unassign = store( address, unassignTransition.get( ).newAddressInfo( ) ) || !unassignTransition.get( ).rollback( );
}
if ( unassign &&
unassignTransition.get( ).oldAddressInfo( ).getOwnerUserId( ) != null &&
unassignTransition.get( ).oldAddressInfo( ).getInstanceId( ) != null ) {
fireDisassociateUsageEvent( unassignTransition.get( ) );
}
if ( unassign && !unassignTransition.get( ).newAddressInfo( ).isAllocated( ) ) {
disable( address.getAddress( ) );
}
return unassign;
}
public boolean start( final Address address, final VmInstance vm ) {
try ( final AddressingBatch batch = batch( ) ) {
boolean start = false;
final Optional<AddressStateTransition> startTransition = address.start( vm );
if ( startTransition.isPresent( ) ) {
start = store( address, startTransition.get( ).newAddressInfo( ) ) || !startTransition.get( ).rollback( );
}
if ( start && startTransition.get( ).newAddressInfo( ).getOwnerUserId( ) != null ) {
final AddressStateTransition transition = startTransition.get( );
fireAssociateUsageEvent( transition );
}
if ( start ) {
addressFlushRequired( );
}
return start;
}
}
public boolean stop( final Address address ) {
try ( final AddressingBatch batch = batch( ) ) {
boolean stop = false;
final Optional<AddressStateTransition> stopTransition = address.stop( );
if ( stopTransition.isPresent( ) ) {
stop = store( address, stopTransition.get( ).newAddressInfo( ) ) || !stopTransition.get( ).rollback( );
}
if ( stop && stopTransition.get( ).oldAddressInfo( ).getOwnerUserId( ) != null ) {
fireDisassociateUsageEvent( stopTransition.get( ) );
}
if ( stop ) {
addressFlushRequired( );
}
return stop;
}
}
public void system( final VmInstance vm ) {
try {
if ( !vm.isUsePrivateAddressing() &&
(VmState.PENDING.equals( vm.getState( ) ) || VmState.RUNNING.equals( vm.getState( ) ) ) ) {
assignSystemAddress( vm );
}
} catch ( final NotEnoughResourcesException e ) {
LOG.warn( "No addresses are available to provide a system address for: " + LogUtil.dumpObject( vm ) );
LOG.debug( e, e );
}
}
public boolean release( final Address address, final String allocationId ) {
try ( final AddressingBatch batch = batch( ) ) {
boolean release = false;
Optional<AddressStateTransition> releaseTransition = Optional.absent( );
while ( address.isAllocated( ) &&
!releaseTransition.isPresent( ) &&
( allocationId==null || allocationId.equals( address.getAllocationId( ) ) ) ) {
releaseTransition = address.release( allocationId );
}
if ( releaseTransition.isPresent( ) ) {
if ( releaseTransition.get( ).oldAddressInfo( ).isReallyAssigned( ) &&
releaseTransition.get( ).oldAddressInfo( ).getInstanceId( ) != null ) {
if ( releaseTransition.get( ).oldAddressInfo( ).getOwnerUserId( ) != null ) {
fireDisassociateUsageEvent( releaseTransition.get( ) );
}
try {
final VmInstance instance = VmInstances.lookup( releaseTransition.get( ).oldAddressInfo( ).getInstanceId( ) );
if ( address.getAddress( ).equals( instance.getPublicAddress( ) ) ) {
Addresses.updatePublicIpByInstanceId( instance.getDisplayName( ), null );
}
try {
system( instance );
} catch ( NoSuchElementException e ) {
LOG.debug( e, e );
} catch ( Exception e ) {
LOG.error( "Error assigning system address for instance " + instance.getDisplayName( ), e );
}
} catch ( NoSuchElementException ex ) {
Logs.extreme( ).error( ex );
}
}
try {
if ( allocatedAddressPersistence.delete(
createEntity( releaseTransition.get( ).oldAddressInfo( ) ),
new Predicate<AllocatedAddressEntity>( ) {
@Override
public boolean apply( final AllocatedAddressEntity addressEntity ) {
return allocationId == null || allocationId.equals( addressEntity.getAllocationId( ) );
}
}
) ) {
release = true;
disable( address.getAddress( ) );
LOG.debug( "Released address: " + String.valueOf( address ) );
final String oldAccountNumber = releaseTransition.get( ).oldAddressInfo( ).getOwnerAccountNumber( );
final String oldUserId = releaseTransition.get( ).oldAddressInfo( ).getOwnerUserId( );
if ( oldAccountNumber != null && oldUserId != null ) {
fireUsageEvent(
UserFullName.getInstanceForAccount( oldAccountNumber, oldUserId ),
address.getDisplayName( ),
Suppliers.ofInstance( AddressEvent.forRelease( ) ) );
}
} else {
releaseTransition.get( ).rollback( );
}
} catch ( final Exception e ) {
LOG.error( "Persistence error for allocated address, attempting rollback " + address.getDisplayName( ), e );
releaseTransition.get( ).rollback( );
}
}
return release;
}
}
public static <R> R withBatch( final Closure<R> closure ) {
//noinspection unused
try ( final AddressingBatch batch = Addresses.getInstance( ).batch( ) ) {
return closure.call( );
}
}
public AddressingBatch batch( ) {
if ( batchThreadLocal.get( ) != null ) {
return new AddressingBatch( ) {
@Override
public void close( ){
}
};
} else {
batchThreadLocal.set( new AddressingBatch( ) );
return batchThreadLocal.get( );
}
}
private void addressFlushRequired( ) {
AddressingBatch.flush( );
}
private Address doAllocateSystemAddress( Optional<String> requestedAddress ) throws NotEnoughResourcesException {
final Optional<Address> address = requestedAddress.isPresent( ) ?
addressRegistry.tryEnable( requestedAddress.get( ) ) :
addressRegistry.tryEnable( );
if ( !address.isPresent( ) ) {
throw new NotEnoughAddressResourcesException( );
}
final Optional<AddressStateTransition> pendingTransition = address.get( ).pendingAssignment( );
if ( pendingTransition.isPresent( ) ) {
try {
call( new Callable<Void>( ) {
@Override
public Void call( ) throws Exception {
allocatedAddressPersistence.save( createEntity( pendingTransition.get( ).newAddressInfo( ) ) );
return null;
}
} );
} catch ( final Exception e ) {
LOG.error( "Persistence error for system address, attempting rollback " + address.get( ).getDisplayName( ), e );
if ( pendingTransition.get( ).rollback( ) ) {
disable( address.get( ).getName( ) );
}
throw new NotEnoughAddressResourcesException( );
}
} else {
LOG.error( "Error transitioning enabled address: " + address );
throw new NotEnoughAddressResourcesException( );
}
return address.get( );
}
static AllocatedAddressEntity createEntity( final Address.AddressInfo addressInfo ) {
final AllocatedAddressEntity entity = AllocatedAddressEntity.create( );
updateEntity( addressInfo, entity );
return entity;
}
static void updateEntity( final Address.AddressInfo addressInfo, final AllocatedAddressEntity entity ) {
if ( entity.getDisplayName( ) != null && !entity.getDisplayName( ).equals( addressInfo.getAddress( ) ) ) {
throw new IllegalArgumentException( "Attempt to update with invalid address" );
}
entity.setDisplayName( addressInfo.getAddress( ) );
entity.setDomain( addressInfo.getDomain( ) );
entity.setState( addressInfo.getState( ) );
if ( addressInfo.getOwnerUserId( ) != null ) {
entity.setOwnerAccountNumber( addressInfo.getOwnerAccountNumber( ) );
entity.setOwnerUserId( addressInfo.getOwnerUserId( ) );
entity.setOwnerUserName( addressInfo.getOwnerUserName( ) );
} else { // system owned
entity.setOwner( Principals.systemFullName( ) );
}
entity.setAllocationId( addressInfo.getAllocationId( ) );
entity.setAssociationId( addressInfo.getAssociationId( ) );
entity.setNetworkInterfaceId( addressInfo.getNetworkInterfaceId( ) );
entity.setNetworkInterfaceOwnerId( addressInfo.getNetworkInterfaceOwnerId( ) );
entity.setPrivateAddress( addressInfo.getPrivateAddress( ) );
entity.setInstanceId( addressInfo.getInstanceId( ) );
entity.setInstanceUuid( addressInfo.getInstanceUuid( ) );
}
private boolean store( final Address address, final AddressInfo addressInfo ) {
try { // Ensure no enclosing transaction.
return call( new Callable<Boolean>( ) {
@Override
public Boolean call() throws Exception {
AllocatedAddressPersistenceException lastException = null;
for ( int i=0; i<Entities.CONCURRENT_UPDATE_RETRIES; i++ ) try {
allocatedAddressPersistence.updateByExample(
AllocatedAddressEntity.exampleWithAddress( addressInfo.getAddress( ) ),
null,
addressInfo.getAddress( ),
new Callback<AllocatedAddressEntity>( ) {
@Override
public void fire( final AllocatedAddressEntity addressEntity ) {
if ( !addressInfo.isAllocated( ) ) {
Entities.delete( addressEntity );
} else {
Addresses.updateEntity( addressInfo, addressEntity );
}
}
}
);
return true;
} catch ( AllocatedAddressPersistenceException e ) {
lastException = e;
if ( address.getStateVersion( ) > addressInfo.getStateVersion( ) || // our update is stale, drop it
( !PersistenceExceptions.isLockError( e ) && !PersistenceExceptions.isStaleUpdate( e ) ) ) {
break;
}
}
if ( lastException != null ) {
LOG.error( "Error storing address " + addressInfo.getAddress( ), lastException );
}
return false;
}
} );
} catch ( ExecutionException e ) {
LOG.error( "Error storing address " + addressInfo.getAddress( ), e );
}
return false;
}
private <T> T call( final Callable<T> callable ) throws ExecutionException {
try {
return Threads.enqueue( Compute.class, Addresses.class, callable ).get( );
} catch ( InterruptedException e ) {
Thread.currentThread( ).interrupt( );
throw new ExecutionException( e );
}
}
private void doAssignSystemAddress( final VmInstance vm ) throws NotEnoughResourcesException {
final String instanceId = vm.getInstanceId();
final Address addr = this.allocateSystemAddress( );
if ( assign( addr, vm ) ) {
updatePublicIpByInstanceId( instanceId, addr.getName( ) );
}
}
private void disable( final String name ) {
try {
addressRegistry.disable( name );
} catch ( NoSuchElementException e ) {
// so nothing to disable
}
checkRemove( name );
}
private void checkRemove( String address ) {
final Iterable<String> configuredAddresses = Addresses.configuredAddresses.get( );
if ( configuredAddresses != null &&
!Iterables.isEmpty( configuredAddresses ) &&
!Iterables.contains( configuredAddresses, address ) ) {
addressRegistry.deregisterDisabled( address );
}
}
/**
* Update addresses from the list assign (system) to instances if necessary.
*/
public void update( final Iterable<String> addressIterable ) {
final Collection<String> addresses = Collections.unmodifiableCollection( Sets.newLinkedHashSet( addressIterable ) );
Addresses.configuredAddresses.set( addresses );
storedAddressLoadingSupplier.get( );
for ( final String address : addresses ) {
lookupOrCreate( address );
}
for ( final Address address : addressRegistry.listDisabledValues( ) ) {
checkRemove( address.getName( ) );
}
}
private Address lookupOrCreate( final String address ) {
Address addr = null;
try {
addr = addressRegistry.lookupDisabled( address );
LOG.trace( "Found address in the inactive set cache: " + addr );
} catch ( final NoSuchElementException e1 ) {
try {
addr = addressRegistry.lookup( address );
LOG.trace( "Found address in the active set cache: " + addr );
} catch ( final NoSuchElementException e ) {}
}
if ( addr == null ) try ( final TransactionResource tx = Entities.transactionFor( VmInstance.class ) ) {
VmInstance vm = maybeFindVm( null, address, null );
addr = new Address( address );
if ( vm != null ) {
addr.pendingAssignment( );
final Optional<AddressStateTransition> transition;
if ( vm.getVpcId( ) == null) {
transition = addr.assign( vm );
} else {
transition = addr.assign( Iterables.getOnlyElement( vm.getNetworkInterfaces( ) ) );
}
if ( transition.isPresent( ) ) {
store( addr, transition.get( ).newAddressInfo( ) );
}
addressRegistry.register( addr );
} else {
addressRegistry.registerDisabled( addr );
}
}
return addr;
}
private VmInstance maybeFindVm( final String instanceId, final String publicIp, final String privateIp ) {
VmInstance vm = null;
if ( instanceId != null ) {
try {
vm = VmInstances.lookup( instanceId );
} catch ( NoSuchElementException ex ) {
Logs.extreme( ).error( ex );
}
}
if ( vm == null && privateIp != null ) {
try {
vm = VmInstances.lookupByPrivateIp( privateIp );
} catch ( NoSuchElementException ex ) {
Logs.extreme( ).error( ex );
}
}
if ( vm == null && publicIp != null ) {
try {
vm = VmInstances.lookupByPublicIp( publicIp );
} catch ( NoSuchElementException ex ) {
Logs.extreme( ).error( ex );
}
}
if ( vm != null && VmState.RUNNING.equals( vm.getState( ) ) && publicIp.equals( vm.getPublicAddress( ) ) ) {
Logs.extreme( ).debug( "Candidate vm which claims this address: " + vm.getInstanceId( ) + " " + vm.getState( ) + " " + publicIp );
if ( publicIp.equals( vm.getPublicAddress( ) ) ) {
Logs.extreme( ).debug( "Found vm which claims this address: " + vm.getInstanceId( ) + " " + vm.getState( ) + " " + publicIp );
}
return vm;
} else {
return null;
}
}
private void loadStoredAddresses( ) {
try {
for ( AllocatedAddressEntity addr : allocatedAddressPersistence.list( null ) ) {
if ( !addressRegistry.contains( addr.getName( ) ) ) {
try {
final Address address = new Address( addr.getName( ) );
if ( addr.getState( ) == null ) {
continue; // state may be null if DB not upgraded
}
if ( addr.getState( ).ordinal( ) < AddressState.allocated.ordinal( ) ) {
allocatedAddressPersistence.delete( addr, Predicates.alwaysTrue( ) );
continue; // do not register
} else if ( addr.getState( ) == AddressState.allocated ) {
address.allocate( addr.getOwnerAccountNumber( ), addr.getOwnerUserId( ), addr.getOwnerUserName( ), addr.domainWithDefault( ), addr.getAllocationId( ) );
} else if ( addr.getState( ) == AddressState.impending ) {
address.pendingAssignment( );
} else if ( addr.getState( ) == AddressState.assigned || addr.getState( ) == AddressState.started ) {
if ( addr.getOwnerAccountNumber( ) == null ||
addr.getOwnerAccountNumber( ).equals( Principals.systemAccount( ).getAccountNumber( ) ) ) {
address.pendingAssignment( );
} else {
address.allocate( addr.getOwnerAccountNumber( ), addr.getOwnerUserId( ), addr.getOwnerUserName( ), addr.domainWithDefault( ), addr.getAllocationId( ) );
}
if ( addr.getNetworkInterfaceId( ) == null ) {
address.assignClassic( addr.getInstanceId( ), addr.getInstanceUuid( ), addr.getPrivateAddress( ) );
} else {
address.assignVpc( addr.getNetworkInterfaceId( ), addr.getNetworkInterfaceOwnerId( ), addr.getPrivateAddress( ), addr.getAssociationId( ) );
if ( addr.getState( ) == AddressState.started ) {
address.start( addr.getInstanceId( ), addr.getInstanceUuid( ) );
}
}
}
addressRegistry.register( address );
} catch ( Exception ex ) {
LOG.error( ex, ex );
}
}
}
} catch ( final Exception e ) {
LOG.debug( e, e );
}
}
private void fireAssociateUsageEvent( final AddressStateTransition transition ) {
final String oldAccountNumber = transition.oldAddressInfo( ).getOwnerAccountNumber( );
final String oldUserId = transition.oldAddressInfo( ).getOwnerUserId( );
if ( oldAccountNumber != null && oldUserId != null ) {
fireUsageEvent(
UserFullName.getInstanceForAccount( oldAccountNumber, oldUserId ),
transition.newAddressInfo( ).getAddress( ),
new Supplier<EventActionInfo<AddressEvent.AddressAction>>( ) {
@Override
public EventActionInfo<AddressEvent.AddressAction> get( ) {
return AddressEvent.forAssociate(
transition.newAddressInfo( ).getInstanceUuid( ),
transition.newAddressInfo( ).getInstanceId( )
);
}
}
);
}
}
private void fireDisassociateUsageEvent( final AddressStateTransition transition ) {
final String oldAccountNumber = transition.oldAddressInfo( ).getOwnerAccountNumber( );
final String oldUserId = transition.oldAddressInfo( ).getOwnerUserId( );
if ( oldAccountNumber != null && oldUserId != null ) {
fireUsageEvent(
UserFullName.getInstanceForAccount( oldAccountNumber, oldUserId ),
transition.oldAddressInfo( ).getAddress( ),
new Supplier<EventActionInfo<AddressEvent.AddressAction>>( ) {
@Override
public EventActionInfo<AddressEvent.AddressAction> get( ) {
return AddressEvent.forDisassociate(
transition.oldAddressInfo( ).getInstanceUuid( ),
transition.oldAddressInfo( ).getInstanceId( )
);
}
}
);
}
}
private void fireUsageEvent( final OwnerFullName ownerFullName,
final String address,
final Supplier<EventActionInfo<AddressEvent.AddressAction>> actionInfoSupplier ) {
if ( !Principals.isFakeIdentityAccountNumber( ownerFullName.getAccountNumber() ) ) {
try {
ListenerRegistry.getInstance( ).fireEvent(
AddressEvent.with(
address,
ownerFullName,
Accounts.lookupAccountAliasById( ownerFullName.getAccountNumber( ) ),
actionInfoSupplier.get() ) );
} catch ( final Throwable e ) {
LOG.error( e, e );
}
}
}
public static class AddressingBatch implements AutoCloseable {
private boolean flushRequested;
@Override
public void close( ) {
if ( flushRequested ) {
flushNow( );
}
reset( );
}
public static void reset( ) {
final AddressingBatch batch = batchThreadLocal.get( );
if ( batch != null ) {
batchThreadLocal.set( null );
batch.flushRequested = false;
}
}
static void flush( ) {
if ( batchThreadLocal.get( ) == null ) {
flushNow( );
} else {
batchThreadLocal.get( ).flushRequested = true;
}
}
static void flushNow( ) {
NetworkInfoBroadcaster.requestNetworkInfoBroadcast( );
}
}
@Resolver( Address.class )
public enum Lookup implements Function<String, Address> {
INSTANCE;
@Override
public Address apply( final String input ) {
Optional<Address> addressOptional = Optional.absent( );
final Function<AddressI,String> addressExtractor =
Strings.startsWith( Address.ID_PREFIX_ALLOC ).apply( input ) ?
allocation( ) :
Strings.startsWith( Address.ID_PREFIX_ASSOC ).apply( input ) ?
association( ) :
null;
if ( addressExtractor != null ) {
addressOptional = Iterables.tryFind(
AddressRegistry.getInstance( ).listValues( ),
CollectionUtils.propertyPredicate( input, addressExtractor )
);
} else try {
addressOptional = Optional.of( AddressRegistry.getInstance( ).lookup( input ) );
} catch ( NoSuchElementException e ) { /* throw appropriate exception below */ }
if ( !addressOptional.isPresent( ) ) {
throw new NoSuchElementException( "Address not found (" + input +")");
}
return addressOptional.get( );
}
}
@QuantityMetricFunction( AddressMetadata.class )
public enum CountAddresses implements Function<OwnerFullName, Long> {
INSTANCE;
@SuppressWarnings( "unchecked" )
@Override
public Long apply( final OwnerFullName input ) {
return (long) Iterables.size( Iterables.filter(
AddressRegistry.getInstance( ).listValues( ),
Predicates.and(
RestrictedTypes.filterByOwner( input ),
Predicates.compose( Predicates.equalTo( Boolean.TRUE ), BooleanFilterFunctions.IS_ALLOCATED )
) ) );
}
}
public static Function<AddressI,String> allocation( ) {
return AllocatedAddressEntity.FilterFunctions.ALLOCATION_ID;
}
public static Function<AddressI,String> association( ) {
return AllocatedAddressEntity.FilterFunctions.ASSOCIATION_ID;
}
public static class Allocator implements Supplier<Address>, Predicate<Address> {
private final Addresses addresses;
private final AddressDomain domain;
private Allocator( final Addresses addresses, final AddressDomain domain ) {
this.addresses = addresses;
this.domain = domain;
}
@Override
public Address get( ) {
final Context ctx = Contexts.lookup( );
try {
return addresses.allocateNext( ctx.getUserFullName( ), domain );
} catch ( final Exception ex ) {
throw Exceptions.toUndeclared( ex );
}
}
@Override
public boolean apply( final Address input ) {
try {
addresses.release( input, input.getAllocationId( ) );
} catch ( final Exception ex ) {
LOG.error( ex, ex );
}
return true;
}
}
public static void updatePublicIpByInstanceId( final String instanceId,
final String publicIp ) {
Entities.asTransaction( VmInstance.class, new Predicate<String>() {
@Override
public boolean apply( final String publicAddress ) {
final VmInstance vm = VmInstances.lookup( instanceId );
VmInstances.updatePublicAddress( vm, Objects.firstNonNull( publicAddress, VmNetworkConfig.DEFAULT_IP ) );
return true;
}
} ).apply( publicIp );
}
@SuppressWarnings( "UnusedDeclaration" )
public static class AddressAvailabilityEventListener implements EventListener<ClockTick> {
public static void register( ) {
Listeners.register(ClockTick.class, new AddressAvailabilityEventListener());
}
@Override
public void fireEvent( final ClockTick event ) {
if ( Bootstrap.isOperational() && Hosts.isCoordinator() ) {
final List<Address> addresses = AddressRegistry.getInstance( ).listValues( );
final List<Address> disabledAddresses = AddressRegistry.getInstance( ).listDisabledValues( );
final long total = addresses.size() + disabledAddresses.size();
final long available = Iterators.size( Iterators.filter( Iterators.concat( addresses.iterator(), disabledAddresses.iterator()), new Predicate<com.eucalyptus.address.Address>() {
@Override
public boolean apply( final Address address ) {
return !address.isAllocated();
}
} ) );
try {
ListenerRegistry.getInstance( ).fireEvent( new ResourceAvailabilityEvent( ResourceType.Address, new ResourceAvailabilityEvent.Availability( total, available ) ) );
} catch ( Exception ex ) {
LOG.error( ex, ex );
}
}
}
}
@TypeMapper
public enum AddressIToAddressInfoTypeTransform implements Function<AddressI,AddressInfoType> {
INSTANCE;
@Override
public AddressInfoType apply( final AddressI address ) {
final AddressInfoType addressInfoType = new AddressInfoType( );
// allocation info
addressInfoType.setPublicIp( address.getAddress( ) );
addressInfoType.setDomain( Objects.firstNonNull( address.getDomain( ), AddressDomain.standard ).toString( ) );
addressInfoType.setAllocationId( address.getAllocationId( ) );
// association info
if ( Strings.startsWith( "i-" ).apply( address.getInstanceId( ) ) ) {
addressInfoType.setInstanceId( address.getInstanceId() );
}
addressInfoType.setAssociationId( address.getAssociationId( ) );
addressInfoType.setNetworkInterfaceId( address.getNetworkInterfaceId( ) );
addressInfoType.setNetworkInterfaceOwnerId( address.getNetworkInterfaceOwnerId( ) );
addressInfoType.setPrivateIpAddress( address.getPrivateAddress( ) );
return addressInfoType;
}
}
private static abstract class AddressIFilterSupport<T extends AddressI> extends FilterSupport<T> {
protected AddressIFilterSupport( @Nonnull final Builder<T> builder ) {
super( properties( builder ) );
}
private static <TI extends AddressI> Builder<TI> properties( final Builder<TI> builder ) {
return builder
.withStringProperty( "allocation-id", ALLOCATION_ID )
.withStringProperty( "association-id", ASSOCIATION_ID )
.withStringProperty( "domain", DOMAIN )
.withStringProperty( "instance-id", AllocatedAddressEntity.FilterFunctions.INSTANCE_ID )
.withStringProperty( "network-interface-id", AllocatedAddressEntity.FilterFunctions.NETWORK_INTERFACE_ID )
.withStringProperty( "network-interface-owner-id", AllocatedAddressEntity.FilterFunctions.NETWORK_INTERFACE_OWNER_ID )
.withStringProperty( "private-ip-address", AllocatedAddressEntity.FilterFunctions.PRIVATE_IP_ADDRESS )
.withStringProperty( "public-ip", AllocatedAddressEntity.FilterFunctions.PUBLIC_IP );
}
}
public static class AddressFilterSupport extends AddressIFilterSupport<Address> {
public AddressFilterSupport( ) {
super( builderFor( Address.class ) );
}
}
public static class AllocatedAddressEntityFilterSupport extends AddressIFilterSupport<AllocatedAddressEntity> {
public AllocatedAddressEntityFilterSupport ( ) {
super( builderFor( AllocatedAddressEntity.class )
.withPersistenceFilter( "allocation-id", "allocationId" )
.withPersistenceFilter( "association-id", "associationId" )
.withPersistenceFilter( "domain", "domain", FUtils.valueOfFunction( AddressDomain.class ) )
.withPersistenceFilter( "instance-id", "instanceId" )
.withPersistenceFilter( "network-interface-id", "networkInterfaceId" )
.withPersistenceFilter( "network-interface-owner-id", "networkInterfaceOwnerId" )
.withPersistenceFilter( "private-ip-address", "privateAddress" )
.withPersistenceFilter( "public-ip", "displayName" )
);
}
}
private enum BooleanFilterFunctions implements Function<Address,Boolean> {
IS_ALLOCATED {
@Nullable
@Override
public Boolean apply( @Nullable final Address address ) {
return address != null && address.isAllocated( );
}
}
}
}