/*************************************************************************
* (c) Copyright 2017 Hewlett Packard Enterprise Development Company LP
*
* 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/.
************************************************************************/
package com.eucalyptus.cassandra;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import com.eucalyptus.cassandra.common.CassandraPersistence;
import com.eucalyptus.cassandra.common.CassandraReplicas;
import com.eucalyptus.cassandra.common.util.CqlUtil;
import com.eucalyptus.util.Either;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.FUtils;
import com.eucalyptus.util.Parameters;
import com.eucalyptus.util.ThrowingFunction;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.Resources;
import javaslang.Function3;
import javaslang.Tuple2;
/**
*
*/
public class CassandraKeyspaces {
private static final ConcurrentMap<String,KeyspaceSpec> keyspaceMap = Maps.newConcurrentMap( );
private static final String KEYSPACE_CQL =
"CREATE KEYSPACE IF NOT EXISTS %1$s WITH REPLICATION = {'class': 'NetworkTopologyStrategy', '%2$s': %3$d};";
private static final String KEYSPACE_RESOURCE_TEMPLATE = "classpath*:*-*-*-%s-*.cql";
/**
* Process all keyspaces, returning any failures.
*/
public static List<Tuple2<String,Optional<Throwable>>> all( final String dc, final int dcNodes ) {
return keyspaceMap.values( ).stream( )
.map( KeyspaceSpec::name )
.sorted( String.CASE_INSENSITIVE_ORDER )
.map( FUtils.tuple( FUtils.eitherThrowable(
Function3.of( CassandraKeyspaces::keyspace ).reversed( ).apply( dcNodes, dc )
).andThen( Either.leftOption( ) ) ) )
.collect( Collectors.toList( ) );
}
/**
* Process the specified keyspace
*/
public static boolean keyspace( final String name, final String dc, final int dcNodes ) {
final KeyspaceSpec keyspaceSpec = keyspaceMap.get( name );
if ( keyspaceSpec == null ) {
throw new IllegalArgumentException( "Unknown keyspace: " + name );
}
return CassandraPersistence.doWithSession(
CassandraPersistence.SessionUsage.Admin,
name,
session -> {
session.execute( String.format(
KEYSPACE_CQL,
keyspaceSpec.name( ),
dc,
keyspaceSpec.replicas( ).replicas( dcNodes ) ) );
scripts( name )
.flatMap( ThrowingFunction.undeclared( CqlUtil::splitCql ).andThen( Collection::stream ) )
.forEach( session::execute );
return true;
} );
}
static void register(
@Nonnull final String keyspace,
@Nonnull final CassandraReplicas replicas
) {
Parameters.checkParamNotNull( "keyspace", keyspace );
keyspaceMap.putIfAbsent( keyspace, new KeyspaceSpec( keyspace, replicas ) );
}
private static Stream<String> scripts( final String keyspace ) {
// discover and sort cql scriptFilenames
final List<String> scripts = Lists.newArrayList( );
final PathMatchingResourcePatternResolver resolver =
new PathMatchingResourcePatternResolver( CassandraKeyspaces.class.getClassLoader( ) );
try {
final String pattern = String.format( KEYSPACE_RESOURCE_TEMPLATE, scriptName( keyspace ) );
final Resource[] resources = resolver.getResources( pattern );
final List<String> scriptFilenames = Lists.newArrayList( );
for ( final Resource resource : resources ) {
scriptFilenames.add( resource.getFilename( ) );
}
Collections.sort( scriptFilenames );
for ( final String resourceName : scriptFilenames ) {
scripts.add( Resources.toString( Resources.getResource( resourceName ), StandardCharsets.UTF_8 ) );
}
} catch ( IOException e ) {
throw Exceptions.toUndeclared( e);
}
return scripts.stream( );
}
private static String scriptName( final String keyspace ) {
return keyspace.replace( '_', '-' ).toLowerCase( );
}
private static final class KeyspaceSpec {
private final String name;
private final CassandraReplicas replicas;
private KeyspaceSpec( final String name, final CassandraReplicas replicas ) {
this.name = Parameters.checkParamNotNullOrEmpty( "name", name );
this.replicas = Parameters.checkParamNotNull( "replicas", replicas );
}
public String name( ) {
return name;
}
public CassandraReplicas replicas( ) {
return replicas;
}
}
}