DATACASS-502 - Add support for keyspace alteration on cluster initialization.

We now support keyspace alteration using XML configuration during after CassandraClusterFactoryBean initialization.

Previously, ALTER keyspace actions resulted in IllegalStateException.
This commit is contained in:
Mark Paluch
2017-10-04 10:43:06 +02:00
parent 8d4949dea4
commit 282bce0c35
5 changed files with 213 additions and 30 deletions

View File

@@ -34,13 +34,16 @@ import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.cassandra.core.cql.CassandraExceptionTranslator;
import org.springframework.data.cassandra.core.cql.CqlTemplate;
import org.springframework.data.cassandra.core.cql.generator.AlterKeyspaceCqlGenerator;
import org.springframework.data.cassandra.core.cql.generator.CreateKeyspaceCqlGenerator;
import org.springframework.data.cassandra.core.cql.generator.DropKeyspaceCqlGenerator;
import org.springframework.data.cassandra.core.cql.keyspace.AlterKeyspaceSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.DropKeyspaceSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceActionSpecification;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -131,6 +134,7 @@ public class CassandraClusterFactoryBean
private @Nullable LatencyTracker latencyTracker;
private List<CreateKeyspaceSpecification> keyspaceCreations = new ArrayList<>();
private List<AlterKeyspaceSpecification> keyspaceAlterations = new ArrayList<>();
private List<DropKeyspaceSpecification> keyspaceDrops = new ArrayList<>();
private Set<KeyspaceActionSpecification> keyspaceSpecifications = new HashSet<>();
private List<KeyspaceActions> keyspaceActions = new ArrayList<>();
@@ -219,7 +223,12 @@ public class CassandraClusterFactoryBean
Optional.ofNullable(latencyTracker).ifPresent(cluster::register);
generateSpecificationsFromFactoryBeans();
executeSpecsAndScripts(keyspaceCreations, startupScripts, cluster);
List<KeyspaceActionSpecification> startup = new ArrayList<>(keyspaceCreations.size() + keyspaceAlterations.size());
startup.addAll(keyspaceCreations);
startup.addAll(keyspaceAlterations);
executeSpecsAndScripts(startup, startupScripts, cluster);
}
/*
@@ -307,6 +316,10 @@ public class CassandraClusterFactoryBean
if (keyspaceActionSpecification instanceof DropKeyspaceSpecification) {
keyspaceDrops.add((DropKeyspaceSpecification) keyspaceActionSpecification);
}
if (keyspaceActionSpecification instanceof AlterKeyspaceSpecification) {
keyspaceAlterations.add((AlterKeyspaceSpecification) keyspaceActionSpecification);
}
});
}
@@ -332,11 +345,22 @@ public class CassandraClusterFactoryBean
}
}
private String toCql(KeyspaceActionSpecification keyspaceActionSpecification) {
private String toCql(KeyspaceActionSpecification specification) {
return (keyspaceActionSpecification instanceof CreateKeyspaceSpecification
? new CreateKeyspaceCqlGenerator((CreateKeyspaceSpecification) keyspaceActionSpecification).toCql()
: new DropKeyspaceCqlGenerator((DropKeyspaceSpecification) keyspaceActionSpecification).toCql());
if (specification instanceof CreateKeyspaceSpecification) {
return new CreateKeyspaceCqlGenerator((CreateKeyspaceSpecification) specification).toCql();
}
if (specification instanceof DropKeyspaceSpecification) {
return new DropKeyspaceCqlGenerator((DropKeyspaceSpecification) specification).toCql();
}
if (specification instanceof AlterKeyspaceSpecification) {
return new AlterKeyspaceCqlGenerator((AlterKeyspaceSpecification) specification).toCql();
}
throw new IllegalArgumentException(
"Unsupported specification type: " + ClassUtils.getQualifiedName(specification.getClass()));
}
/*

View File

@@ -23,10 +23,12 @@ import java.util.List;
import java.util.Map;
import org.springframework.data.cassandra.core.cql.KeyspaceIdentifier;
import org.springframework.data.cassandra.core.cql.keyspace.AlterKeyspaceSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.DataCenterReplication;
import org.springframework.data.cassandra.core.cql.keyspace.DefaultOption;
import org.springframework.data.cassandra.core.cql.keyspace.DropKeyspaceSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceActionSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceOption;
import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceOption.ReplicationStrategy;
import org.springframework.data.cassandra.core.cql.keyspace.Option;
@@ -83,25 +85,77 @@ class KeyspaceActionSpecificationFactory {
CreateKeyspaceSpecification create = CreateKeyspaceSpecification.createKeyspace(name).ifNotExists(ifNotExists)
.with(KeyspaceOption.DURABLE_WRITES, durableWrites);
Map<Option, Object> replicationStrategyMap = new HashMap<>();
replicationStrategyMap.put(new DefaultOption("class", String.class, true, false, true),
replicationStrategy.getValue());
Map<Option, Object> replication = getReplication();
if (replicationStrategy == ReplicationStrategy.SIMPLE_STRATEGY) {
replicationStrategyMap.put(new DefaultOption("replication_factor", Long.class, true, false, false),
replicationFactor);
if (!replication.isEmpty()) {
create.with(KeyspaceOption.REPLICATION, replication);
}
if (replicationStrategy == ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY) {
for (DataCenterReplication datacenter : replications) {
replicationStrategyMap.put(new DefaultOption(datacenter.getDataCenter(), Long.class, true, false, false),
datacenter.getReplicationFactor());
return create;
}
/**
* Generate a {@link AlterKeyspaceSpecification} for the keyspace.
*
* @return the {@link AlterKeyspaceSpecification}.
* @since 2.0.1
*/
public KeyspaceActionSpecification alter() {
AlterKeyspaceSpecification alter = AlterKeyspaceSpecification.alterKeyspace(name)
.with(KeyspaceOption.DURABLE_WRITES, durableWrites);
Map<Option, Object> replication = getReplication();
if (!replication.isEmpty()) {
alter.with(KeyspaceOption.REPLICATION, replication);
}
return alter;
}
/**
* Create replication options represented as {@link Map}.
*
* @return the replication options represented as {@link Map}.
* @since 2.0.1
*/
protected Map<Option, Object> getReplication() {
Map<Option, Object> replicationStrategyMap = new HashMap<>();
if (hasReplicationOptions()) {
replicationStrategyMap.put(new DefaultOption("class", String.class, true, false, true),
replicationStrategy.getValue());
if (replicationStrategy == ReplicationStrategy.SIMPLE_STRATEGY) {
replicationStrategyMap.put(new DefaultOption("replication_factor", Long.class, true, false, false),
replicationFactor);
}
if (replicationStrategy == ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY) {
for (DataCenterReplication datacenter : replications) {
replicationStrategyMap.put(new DefaultOption(datacenter.getDataCenter(), Long.class, true, false, false),
datacenter.getReplicationFactor());
}
}
}
create.with(KeyspaceOption.REPLICATION, replicationStrategyMap);
return replicationStrategyMap;
}
return create;
private boolean hasReplicationOptions() {
if (replicationStrategy == ReplicationStrategy.SIMPLE_STRATEGY && replicationFactor > 0) {
return true;
}
if (replicationStrategy == ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY && !replications.isEmpty()) {
return true;
}
return false;
}
/**

View File

@@ -63,8 +63,8 @@ public class KeyspaceActionSpecificationFactoryBean implements FactoryBean<Keysp
@Override
public void afterPropertiesSet() {
Assert.hasText(name, "Keyspace Name is required for a Keyspace Action");
Assert.notNull(action, "Keyspace Action is required for a Keyspace Action");
Assert.hasText(name, "Keyspace name is required for a keyspace action");
Assert.notNull(action, "Keyspace action is required for a keyspace action");
KeyspaceActionSpecificationFactoryBuilder builder = KeyspaceActionSpecificationFactory.builder(name)
.durableWrites(durableWrites);
@@ -84,16 +84,20 @@ public class KeyspaceActionSpecificationFactoryBean implements FactoryBean<Keysp
KeyspaceActionSpecificationFactory factory = builder.build();
this.actions = createActions(factory);
}
private KeyspaceActions createActions(KeyspaceActionSpecificationFactory factory) {
switch (action) {
case NONE:
this.actions = new KeyspaceActions();
break;
return new KeyspaceActions();
case CREATE_DROP:
this.actions = new KeyspaceActions(factory.create(ifNotExists), factory.drop(ifNotExists));
break;
return new KeyspaceActions(factory.create(ifNotExists), factory.drop(ifNotExists));
case CREATE:
this.actions = new KeyspaceActions(factory.create(ifNotExists));
break;
return new KeyspaceActions(factory.create(ifNotExists));
case ALTER:
return new KeyspaceActions(factory.alter());
default:
throw new IllegalStateException(String.format("KeyspaceAction %s not supported", action));
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.config;
import static org.assertj.core.api.Assertions.*;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.springframework.data.cassandra.core.cql.KeyspaceIdentifier;
import org.springframework.data.cassandra.core.cql.keyspace.AlterKeyspaceSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceActionSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceOption.ReplicationStrategy;
/**
* Unit tests for {@link KeyspaceActionSpecificationFactoryBean}.
*
* @author Mark Paluch
*/
public class KeyspaceActionSpecificationFactoryBeanUnitTests {
KeyspaceActionSpecificationFactoryBean bean = new KeyspaceActionSpecificationFactoryBean();
@Test // DATACASS-502
public void shouldAlterKeyspace() {
bean.setAction(KeyspaceAction.ALTER);
bean.setDurableWrites(true);
bean.setName("my_keyspace");
bean.setNetworkTopologyDataCenters(Arrays.asList("foo", "bar"));
bean.setNetworkTopologyReplicationFactors(Arrays.asList("1", "2"));
bean.setReplicationStrategy(ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY);
bean.afterPropertiesSet();
List<KeyspaceActionSpecification> actions = bean.getObject().getActions();
assertThat(actions).hasSize(1).hasAtLeastOneElementOfType(AlterKeyspaceSpecification.class);
AlterKeyspaceSpecification alter = (AlterKeyspaceSpecification) actions.get(0);
assertThat(alter.getName()).isEqualTo(KeyspaceIdentifier.of("my_keyspace"));
assertThat(alter.getOptions()).containsKeys("durable_writes", "replication");
}
@Test // DATACASS-502
public void shouldAlterKeyspaceWithSimpleReplication() {
bean.setAction(KeyspaceAction.ALTER);
bean.setDurableWrites(true);
bean.setName("my_keyspace");
bean.setReplicationFactor(5);
bean.afterPropertiesSet();
List<KeyspaceActionSpecification> actions = bean.getObject().getActions();
assertThat(actions).hasSize(1).hasAtLeastOneElementOfType(AlterKeyspaceSpecification.class);
AlterKeyspaceSpecification alter = (AlterKeyspaceSpecification) actions.get(0);
assertThat(alter.getName()).isEqualTo(KeyspaceIdentifier.of("my_keyspace"));
assertThat(alter.getOptions()).containsKeys("durable_writes", "replication");
}
@Test // DATACASS-502
public void shouldAlterKeyspaceWithoutReplication() {
bean.setAction(KeyspaceAction.ALTER);
bean.setDurableWrites(true);
bean.setName("my_keyspace");
bean.afterPropertiesSet();
List<KeyspaceActionSpecification> actions = bean.getObject().getActions();
assertThat(actions).hasSize(1).hasAtLeastOneElementOfType(AlterKeyspaceSpecification.class);
AlterKeyspaceSpecification alter = (AlterKeyspaceSpecification) actions.get(0);
assertThat(alter.getName()).isEqualTo(KeyspaceIdentifier.of("my_keyspace"));
assertThat(alter.getOptions()).doesNotContainKeys("replication");
}
}

View File

@@ -20,11 +20,15 @@
<cass:data-center name="bar" replication-factor="2" />
</cass:replication>
</cass:keyspace>
<cass:keyspace action="CREATE_DROP" durable-writes="true"
name="full2">
<cass:replication class="SIMPLE_STRATEGY">
<cass:data-center name="foo" replication-factor="1" />
<cass:data-center name="bar" replication-factor="2" />
<cass:keyspace action="CREATE_DROP" durable-writes="false"
name="full2">
<cass:replication class="SIMPLE_STRATEGY" replication-factor="2"/>
</cass:keyspace>
<cass:keyspace action="ALTER" durable-writes="true"
name="full2">
<cass:replication class="NETWORK_TOPOLOGY_STRATEGY">
<cass:data-center name="foo" replication-factor="4"/>
<cass:data-center name="bar" replication-factor="4"/>
</cass:replication>
</cass:keyspace>
<cass:startup-cql><![CDATA[