/******************************************************************************* *Copyright (c) 2009 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, only version 3 of the License. * * * This file 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., 130 Castilian * Dr., Goleta, CA 93101 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. *******************************************************************************/ /* * Author: chris grzegorczyk <grze@eucalyptus.com> */ package com.eucalyptus.address; import java.lang.reflect.Constructor; import java.util.NoSuchElementException; import java.util.UUID; import java.util.concurrent.atomic.AtomicMarkableReference; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.PersistenceContext; import javax.persistence.Table; import javax.persistence.Transient; import org.apache.log4j.Logger; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import com.eucalyptus.bootstrap.Component; import com.eucalyptus.cluster.VmInstance; import com.eucalyptus.cluster.VmInstances; import com.eucalyptus.cluster.callback.AssignAddressCallback; import com.eucalyptus.cluster.callback.QueuedEventCallback; import com.eucalyptus.cluster.callback.UnassignAddressCallback; import com.eucalyptus.entities.EntityWrapper; import com.eucalyptus.util.HasName; import com.eucalyptus.util.LogUtil; import edu.ucsb.eucalyptus.msgs.DescribeAddressesResponseItemType; import com.eucalyptus.records.EventClass; import com.eucalyptus.records.EventRecord; import com.eucalyptus.records.EventType; @Entity @PersistenceContext( name = "eucalyptus_general" ) @Table( name = "addresses" ) @Cache( usage = CacheConcurrencyStrategy.READ_WRITE ) public class Address implements HasName<Address> { public enum State { broken, unallocated, allocated, assigned, impending; } public enum Transition { allocating { public Class getCallback( ) { return QueuedEventCallback.NOOP.class; } }, unallocating { public Class getCallback( ) { return QueuedEventCallback.NOOP.class; } }, assigning { @Override public Class getCallback( ) { return AssignAddressCallback.class; } }, unassigning { @Override public Class getCallback( ) { return UnassignAddressCallback.class; } }, system { public Class getCallback( ) { return QueuedEventCallback.NOOP.class; } }, quiescent { public Class getCallback( ) { return QueuedEventCallback.NOOP.class; } }; public abstract Class getCallback( ); } private static Logger LOG = Logger.getLogger( Address.class ); @Id @GeneratedValue @Column( name = "address_id" ) private Long id = -1l; @Column( name = "address_name" ) private String name; @Column( name = "address_cluster" ) private String cluster; @Column( name = "address_owner_id" ) private String userId; @Transient private String instanceId; @Transient private String instanceAddress; @Transient public static String UNALLOCATED_USERID = "nobody"; @Transient public static String UNASSIGNED_INSTANCEID = "available"; @Transient public static String UNASSIGNED_INSTANCEADDR = "0.0.0.0"; @Transient public static String PENDING_ASSIGNMENT = "pending"; @Transient private final AtomicMarkableReference<State> state = new AtomicMarkableReference<State>( State.unallocated, false ) { public String toString() { return state.getReference( )+":pending="+state.isMarked( ); } }; @Transient private transient final SplitTransition QUIESCENT = new SplitTransition( Transition.quiescent ) { public void bottom( ) {} public void top( ) {} public String toString( ) { return ""; } }; @Transient private volatile SplitTransition transition; @Transient private volatile String transitionId = PENDING_ASSIGNMENT; public Address( ) {} public Address( final String name ) { this( ); this.name = name; } public Address( String address, String cluster ) { this( address ); this.userId = UNALLOCATED_USERID; this.instanceId = UNASSIGNED_INSTANCEID; this.instanceAddress = UNASSIGNED_INSTANCEADDR; this.cluster = cluster; this.transition = QUIESCENT; this.init( ); } public Address( String address, String cluster, String userId, String instanceId, String instanceAddress ) { this( address ); this.cluster = cluster; this.userId = userId; this.instanceId = instanceId; this.instanceAddress = instanceAddress; this.transition = QUIESCENT; this.init( ); } public void init( ) {//Should only EVER be called externally after loading from the db this.transition = QUIESCENT; if ( this.userId == null ) { this.userId = UNALLOCATED_USERID; } if ( this.instanceAddress == null || this.instanceId == null ) { this.instanceAddress = UNASSIGNED_INSTANCEADDR; this.instanceId = UNASSIGNED_INSTANCEID; } if ( UNALLOCATED_USERID.equals( this.userId ) ) { this.state.set( State.unallocated, true ); this.instanceAddress = UNASSIGNED_INSTANCEADDR; this.instanceId = UNASSIGNED_INSTANCEID; Addresses.getInstance( ).registerDisabled( this ); this.state.set( State.unallocated, false ); } else if ( !this.instanceId.equals( UNASSIGNED_INSTANCEID ) ) { this.state.set( State.assigned, true ); Addresses.getInstance( ).register( this ); this.state.set( State.assigned, false ); } else { this.state.set( State.allocated, true ); if ( this.isSystemOwned( ) ) { Addresses.getInstance( ).registerDisabled( this ); this.userId = UNALLOCATED_USERID; this.instanceAddress = UNASSIGNED_INSTANCEADDR; this.instanceId = UNASSIGNED_INSTANCEID; Address.removeAddress( this.name ); this.state.set( State.unallocated, false ); } else { Addresses.getInstance( ).register( this ); this.state.set( State.allocated, false ); } } LOG.debug( "Initialized address: " + this.toString( ) ); } private boolean transition( State expectedState, State newState, boolean expectedMark, boolean newMark, SplitTransition transition ) { this.transition = transition; this.transitionId = UUID.randomUUID( ).toString( ); EventRecord.caller( this.getClass( ), EventType.ADDRESS_STATE, this.state.getReference( ), this.toString( ) ).debug( ); if ( !this.state.compareAndSet( expectedState, newState, expectedMark, newMark ) ) { throw new IllegalStateException( String.format( "Cannot mark address as %s[%s.%s->%s.%s] when it is %s.%s: %s", transition.getName( ), expectedState, expectedMark, newState, newMark, this.state.getReference( ), this.state.isMarked( ), this.toString( ) ) ); } EventRecord.caller( this.getClass( ), EventType.ADDRESS_STATE, this.state.getReference( ), "TOP", this.transition.getName( ).name( ), this.toString( ) ) .debug( ); this.transition.top( ); return true; } public Address allocate( final String userId ) { this.transition( State.unallocated, State.allocated, false, true, new SplitTransition( Transition.allocating ) { public void top( ) { Address.this.instanceId = UNASSIGNED_INSTANCEID; Address.this.instanceAddress = UNASSIGNED_INSTANCEADDR; Address.this.userId = userId; try { Addresses.getInstance( ).enable( Address.this.name ); } catch ( NoSuchElementException e ) { try { Addresses.getInstance( ).register( Address.this ); } catch ( NoSuchElementException e1 ) { LOG.debug( e ); } } if( !Address.this.isSystemOwned( ) ) { Address.addAddress( Address.this ); } EventRecord.here( Address.class, EventClass.ADDRESS, EventType.ADDRESS_ALLOCATE, "user=" + Address.this.userId, "address=" + Address.this.name, Address.this.isSystemOwned( ) ? "SYSTEM" : "USER" ).info( ); Address.this.state.attemptMark( State.allocated, false ); super.bottom( ); } public void bottom( ) {} } ); return this; } public Address release( ) { SplitTransition release = new SplitTransition( Transition.unallocating ) { public void top( ) { EventRecord.here( Address.class, EventClass.ADDRESS, EventType.ADDRESS_RELEASE, "user=" + Address.this.userId, "address=" + Address.this.name, Address.this.isSystemOwned( ) ? "SYSTEM" : "USER" ).info( ); Address.this.instanceId = UNASSIGNED_INSTANCEID; Address.this.instanceAddress = UNASSIGNED_INSTANCEADDR; Address.this.userId = UNALLOCATED_USERID; Address.removeAddress( Address.this.name ); super.bottom( ); } }; if( State.impending.equals( this.state.getReference( ) ) ) { this.transition( State.impending, State.unallocated, true, true, release ); } else if( State.unallocated.equals( this.state.getReference( ) ) ) { return this; } else { this.transition( State.allocated, State.unallocated, false, true, release ); } return this; } private static void removeAddress( String name ) { try { Addresses.getInstance( ).disable( name ); } catch ( NoSuchElementException e1 ) { LOG.debug( e1 ); } EntityWrapper<Address> db = new EntityWrapper<Address>( ); try { Address dbAddr = db.getUnique( new Address( name ) ); db.delete( dbAddr ); db.commit( ); } catch ( Throwable e ) { db.rollback( ); } } public Address unassign( ) { if( this.isSystemOwned( ) ) { this.transition( State.assigned, State.unallocated, false, true, // new SplitTransition( Transition.unassigning ) { @Override public void top( ) {} @Override public void bottom( ) { EventRecord.here( Address.class, EventClass.ADDRESS, EventType.ADDRESS_RELEASE, "user=" + Address.this.userId ) .append( "address=" + Address.this.name, "instance=" + Address.this.instanceId, "instance-address=" ) .append( Address.this.instanceAddress, "SYSTEM" ).info( ); Address.this.instanceId = UNASSIGNED_INSTANCEID; Address.this.instanceAddress = UNASSIGNED_INSTANCEADDR; Address.this.userId = UNALLOCATED_USERID; Address.removeAddress( Address.this.name ); super.bottom( ); } } ); } else { this.transition( State.assigned, State.allocated, false, true, new SplitTransition( Transition.unassigning ) { public void top( ) {} public void bottom( ) { try { VmInstance vm = VmInstances.getInstance( ).lookup( Address.this.getInstanceId( ) ); EventRecord.here( Address.class, EventClass.ADDRESS, EventType.ADDRESS_UNASSIGN, "user=" + vm.getOwnerId( ) ) .append( "address=" + Address.this.name, "instance=" + Address.this.instanceId, "instance-address=" ) .append( Address.this.instanceAddress, "SYSTEM" ).info( ); } catch ( NoSuchElementException e ) { EventRecord.here( Address.class, EventClass.ADDRESS, EventType.ADDRESS_UNASSIGN, "user=<unknown>" ) .append( "address=" + Address.this.name, "instance=" + Address.this.instanceId, "instance-address=" ) .append( Address.this.instanceAddress, "SYSTEM" ).info( ); } Address.this.instanceId = UNASSIGNED_INSTANCEID; Address.this.instanceAddress = UNASSIGNED_INSTANCEADDR; super.bottom( ); } } ); } return this; } public Address pendingAssignment( ) { this.transition( State.unallocated, State.impending, false, true, new SplitTransition( Transition.system ) { public void top( ) { Address.this.instanceId = PENDING_ASSIGNMENT; Address.this.instanceAddress = UNASSIGNED_INSTANCEADDR; Address.this.userId = "eucalyptus"; try { Addresses.getInstance( ).enable( Address.this.name ); } catch ( NoSuchElementException e ) { try { Addresses.getInstance( ).register( Address.this ); } catch ( NoSuchElementException e1 ) { LOG.debug( e ); } } EventRecord.here( Address.class, EventClass.ADDRESS, EventType.ADDRESS_PENDING, "user="+Address.this.userId ) .append( "address="+Address.this.name, "instance="+Address.this.instanceId, "instance-address=" ) .append( Address.this.instanceAddress, "SYSTEM" ).info( ); } @Override public void bottom( ) {} } ); return this; } public Address assign( final String instanceId, final String instanceAddr ) { if( this.state.compareAndSet( State.impending, State.impending, true, true ) ) { this.transition( State.impending, State.assigned, true, true, // new SplitTransition( Transition.assigning ) { public void top( ) { Address.this.setInstanceId( instanceId ); Address.this.setInstanceAddress( instanceAddr ); } @Override public void bottom( ) { EventRecord.here( Address.class, EventClass.ADDRESS, EventType.ADDRESS_ASSIGN, "user="+Address.this.userId ) .append( "address="+Address.this.name, "instance="+Address.this.instanceId, "instance-address=" ) .append( Address.this.instanceAddress, "SYSTEM" ).info( ); super.bottom( ); } } ); } else { this.transition( State.allocated, State.assigned, false, true, // new SplitTransition( Transition.assigning ) { public void top( ) { Address.this.setInstanceId( instanceId ); Address.this.setInstanceAddress( instanceAddr ); } @Override public void bottom( ) { try { VmInstance vm = VmInstances.getInstance( ).lookup( Address.this.getInstanceId( ) ); EventRecord.here( Address.class, EventClass.ADDRESS, EventType.ADDRESS_ASSIGN, "user=" + vm.getOwnerId( ) ) .append( "address=" + Address.this.name, "instance=" + Address.this.instanceId, "instance-address=" ) .append( Address.this.instanceAddress, "USER" ).info( ); } catch ( NoSuchElementException e ) { EventRecord.here( Address.class, EventClass.ADDRESS, EventType.ADDRESS_ASSIGN, "user=<unknown>" ) .append( "address=" + Address.this.name, "instance=" + Address.this.instanceId, "instance-address=" ) .append( Address.this.instanceAddress, "USER" ).info( ); } super.bottom( ); } } ); } return this; } public Transition getTransition( ) { return this.transition.getName( ); } public QueuedEventCallback getCallback( ) { try { Class cbClass = this.transition.getName( ).getCallback( ); Constructor cbCons = cbClass.getConstructor( Address.class ); return ( QueuedEventCallback ) cbCons.newInstance( this ); } catch ( Exception e ) { LOG.debug( e, e ); return new QueuedEventCallback.NOOP( ); } } public Address clearPending( ) { if ( !this.state.isMarked( ) ) { throw new IllegalStateException( "Trying to clear an address which is not currently pending." ); } else { EventRecord.caller( this.getClass( ), EventType.ADDRESS_STATE, this.state.getReference( ), "BOTTOM", this.transition.getName( ).name( ), this.toString( ) ) .debug( ); try { this.transition.bottom( ); } finally { this.transition = QUIESCENT; this.transitionId = PENDING_ASSIGNMENT; } } return this; } public boolean isAllocated( ) { return this.state.getReference( ).ordinal( ) > State.unallocated.ordinal( ); } public boolean isSystemOwned( ) { return Component.eucalyptus.name( ).equals( this.getUserId( ) ); } public boolean isAssigned( ) { return this.state.getReference( ).ordinal( ) > State.allocated.ordinal( ); } public boolean isPending( ) { return this.state.isMarked( ); } private static void addAddress( Address address ) { EntityWrapper<Address> db = new EntityWrapper<Address>( ); try { Address addr = db.getUnique( new Address( address.getName( ) ) ); addr.setUserId( address.getUserId( ) ); db.commit( ); } catch ( Throwable e ) { try { db.add( address ); db.commit( ); } catch ( Throwable e1 ) { db.rollback( ); } } } public String getInstanceId( ) { return this.instanceId; } public String getName( ) { return this.name; } public String getCluster( ) { return cluster; } public String getUserId( ) { return userId; } public String getInstanceAddress( ) { return instanceAddress; } private void setInstanceAddress( String instanceAddress ) { this.instanceAddress = instanceAddress; } public void setUserId( final String userId ) { this.userId = userId; } public Long getId( ) { return id; } public void setId( final Long id ) { this.id = id; } public void setCluster( final String cluster ) { this.cluster = cluster; } public void setInstanceId( String instanceId ) { this.instanceId = instanceId; } public void setName(String name) { this.name = name; } @Override public String toString( ) { return "Address " + this.name + " " + this.cluster + " " + (this.isAllocated( )?this.userId + " ":"") + (this.isAssigned( )? this.instanceId + " " + this.instanceAddress + " ":"") + " " + this.state.getReference( ) + (this.state.isMarked( )?":pending":"") + this.transition; } @Override public boolean equals( final Object o ) { if ( this == o ) return true; if ( !( o instanceof Address ) ) return false; Address address = ( Address ) o; if ( !name.equals( address.name ) ) return false; return true; } @Override public int hashCode( ) { return name.hashCode( ); } public DescribeAddressesResponseItemType getDescription( boolean isAdmin ) { String name = this.getName( ); String desc = null; if ( isAdmin ) { desc = String.format( "%s (%s)", PENDING_ASSIGNMENT.equals( this.getInstanceId( ) ) ? UNASSIGNED_INSTANCEID : this.getInstanceId( ), this.getUserId( ) ); } else { desc = UNASSIGNED_INSTANCEID.equals( this.getInstanceId( ) ) || PENDING_ASSIGNMENT.equals( this.getInstanceId( ) ) ? null : this.getInstanceId( ); } return new DescribeAddressesResponseItemType( name, desc ); } public abstract class SplitTransition { private Transition t; private State previous; public SplitTransition( Transition t ) { this.t = t; this.previous = Address.this.state != null ? Address.this.state.getReference( ) : State.unallocated; } private Transition getName( ) { return this.t; } public abstract void top( ); public void bottom( ) { Address.this.state.set( Address.this.state.getReference( ), false ); } @Override public String toString( ) { return String.format( "[SplitTransition previous=%s, transition=%s, next=%s, pending=%s, transitionId=%s]", this.previous, this.t, Address.this.state.getReference( ), Address.this.state.isMarked( ), Address.this.transitionId ); } } @Override public int compareTo( Address that ) { return this.getName( ).compareTo( that.getName( ) ); } }