/*************************************************************************
* Copyright 2009-2016 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.network.applicator;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Date;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import org.apache.log4j.Logger;
import com.eucalyptus.bootstrap.Bootstrap;
import com.eucalyptus.bootstrap.OrderedShutdown;
import com.eucalyptus.cluster.NetworkInfo;
import com.eucalyptus.component.Faults;
import com.eucalyptus.component.id.Eucalyptus;
import com.eucalyptus.crypto.util.Timestamps;
import com.eucalyptus.network.NetworkGroups;
import com.eucalyptus.network.NetworkMode;
import com.eucalyptus.system.BaseDirectory;
import com.eucalyptus.util.Pair;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
/**
*
*/
@SuppressWarnings( { "Guava", "WeakerAccess" } )
public class AppliedVersionApplicator extends ModeSpecificApplicator {
private static final String APPLIED_VERSION_FILE = "global_network_info.version";
private static final Logger logger = Logger.getLogger( AppliedVersionApplicator.class );
private static final AtomicReference<Pair<WatchService,WatchKey>> watchContext = new AtomicReference<>( );
private static final AtomicReference<Pair<Long,String>> lastAppliedVersion = new AtomicReference<>( );
private static final Supplier<String> faultSupplier = Suppliers.memoizeWithExpiration(
() -> Faults.forComponent( Eucalyptus.class ).havingId( 1016 ).log( ),
15, TimeUnit.MINUTES );
static {
OrderedShutdown.registerPreShutdownHook( AppliedVersionApplicator::shutdown );
}
public AppliedVersionApplicator( ) {
super( EnumSet.of( NetworkMode.VPCMIDO ) );
}
@Override
protected void modeApply(
final NetworkMode mode,
final ApplicatorContext context,
final ApplicatorChain chain
) throws ApplicatorException {
final NetworkInfo info = context.getNetworkInfo( );
final Pair<Long,String> lastAppliedVersion = getLastAppliedVersion( );
boolean alreadyApplied = false;
if ( lastAppliedVersion != null ) {
info.setAppliedTime( Timestamps.formatIso8601Timestamp( new Date( lastAppliedVersion.getLeft( ) ) ) );
info.setAppliedVersion( lastAppliedVersion.getRight( ) );
MarshallingApplicatorHelper.clearMarshalledNetworkInfoCache( context );
alreadyApplied = info.getVersion( ).equals( lastAppliedVersion.getRight( ) );
}
// initial broadcast
chain.applyNext( context );
// wait for eucanetd to apply
boolean applied = false;
final Path path = BaseDirectory.RUN.getChildFile( APPLIED_VERSION_FILE ).toPath( );
final long until = System.currentTimeMillis( ) + TimeUnit.SECONDS.toMillis( NetworkGroups.MAX_BROADCAST_APPLY );
long now;
waitloop:
while( !Bootstrap.isShuttingDown( ) && !alreadyApplied && ( now = System.currentTimeMillis( ) ) < until ) try {
final WatchKey key = getWatchService( ).poll( until - now, TimeUnit.MILLISECONDS );
if ( key != null && key.isValid( ) ) try {
for ( final WatchEvent<?> event: key.pollEvents( ) ) {
if ( path.getFileName( ).equals( event.context( ) ) ) {
try {
final Pair<Long,String> appliedVersion = readAppliedVersion( path );
if ( !info.getVersion( ).equals( appliedVersion.getRight( ) ) ) {
continue waitloop; // wait until timeout
}
info.setAppliedTime( Timestamps.formatIso8601Timestamp( new Date( appliedVersion.getLeft( ) ) ) );
info.setAppliedVersion( appliedVersion.getRight( ) );
MarshallingApplicatorHelper.clearMarshalledNetworkInfoCache( context );
applied = true;
break waitloop;
} catch ( IOException e ) {
logger.error( "Error reading last applied network broadcast version" );
}
}
}
} finally {
key.reset( );
}
} catch ( ClosedWatchServiceException | InterruptedException e ) {
break;
}
// broadcast with updated info
if ( applied ) {
chain.applyNext( context );
} else if ( !alreadyApplied ) {
faultSupplier.get( );
}
}
@Nullable
private Pair<Long,String> getLastAppliedVersion( ) throws ApplicatorException {
Pair<Long,String> lastApplied = lastAppliedVersion.get( );
final Path path = BaseDirectory.RUN.getChildFile( APPLIED_VERSION_FILE ).toPath( );
if ( lastApplied == null && path.toFile( ).canRead( ) ) {
try {
lastApplied = readAppliedVersion( path );
} catch ( IOException e ) {
throw new ApplicatorException( "Error reading applied version", e );
}
}
return lastApplied;
}
private Pair<Long,String> readAppliedVersion( final Path path ) throws IOException {
final String appliedVersion =
com.google.common.io.Files.toString( path.toFile( ), StandardCharsets.UTF_8 ).trim( );
final long appliedTime = System.currentTimeMillis( );
final Pair<Long,String> appliedVersionPair = Pair.pair( appliedTime, appliedVersion );
lastAppliedVersion.set( appliedVersionPair );
return appliedVersionPair;
}
private WatchService getWatchService( ) throws ApplicatorException {
Pair<WatchService,WatchKey> watchPair = watchContext.get( );
if ( watchPair == null ) try {
final Path path = BaseDirectory.RUN.getFile( ).toPath( );
final WatchService watcher = path.getFileSystem( ).newWatchService( );
final WatchKey watckKey = path.register( watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY
);
watchPair = Pair.pair( watcher, watckKey );
watchContext.set( watchPair );
} catch ( IOException e ) {
throw new ApplicatorException( "Error setting up file watch", e );
}
return watchPair.getLeft( );
}
private static void shutdown( ) {
final Pair<WatchService,WatchKey> watchPair = watchContext.get( );
if ( watchPair != null ) {
watchPair.getRight( ).cancel( );
try {
watchPair.getLeft( ).close( );
} catch ( IOException e ) {
logger.error( "Error closing watch service", e );
}
}
}
}