From dfa792f5a9f65d0a21173701d0c87579f1a1a42f Mon Sep 17 00:00:00 2001 From: Dennis Kline Date: Sun, 4 Nov 2018 15:22:40 -0500 Subject: [PATCH] Allow for automatically blacklisting any TWCS or DTCS tables within a keyspace Fixes #577. ref: - https://github.com/thelastpickle/cassandra-reaper/pull/585 - https://github.com/thelastpickle/cassandra-reaper/pull/602 --- .../docs/configuration/reaper_specific.md | 12 + .../cassandra-reaper-cassandra-ssl.yaml | 3 +- .../resource/cassandra-reaper-cassandra.yaml | 3 +- .../resource/cassandra-reaper-h2.yaml | 3 +- .../resource/cassandra-reaper-memory.yaml | 3 +- .../resource/cassandra-reaper-postgres.yaml | 3 +- src/packaging/resource/cassandra-reaper.yaml | 3 +- .../ReaperApplicationConfiguration.java | 15 +- .../java/io/cassandrareaper/core/Table.java | 77 +++++ .../java/io/cassandrareaper/jmx/JmxProxy.java | 5 +- .../io/cassandrareaper/jmx/JmxProxyImpl.java | 14 +- .../resources/RepairRunResource.java | 62 +++-- .../resources/RepairScheduleResource.java | 24 +- .../service/ClusterRepairScheduler.java | 17 +- .../service/RepairRunService.java | 5 +- .../service/RepairUnitService.java | 72 ++++- .../service/SegmentRunner.java | 40 +-- .../ReaperApplicationConfigurationTest.java | 3 +- .../acceptance/BasicSteps.java | 73 ++++- .../resources/ClusterResourceTest.java | 10 +- .../resources/RepairRunResourceTest.java | 115 +++++++- .../service/ClusterRepairSchedulerTest.java | 31 ++- .../service/RepairUnitServiceTest.java | 262 ++++++++++++++++++ .../service/SegmentRunnerTest.java | 73 +---- ...ndra-reaper-at-access-control-enabled.yaml | 3 +- .../test/resources/cassandra-reaper-at.yaml | 3 +- .../cassandra-reaper-cassandra-at.yaml | 3 +- .../resources/cassandra-reaper-h2-at.yaml | 3 +- .../cassandra-reaper-postgres-at.yaml | 3 +- .../src/test/resources/cassandra-reaper.yaml | 5 +- .../integration_reaper_functionality.feature | 22 ++ 31 files changed, 765 insertions(+), 205 deletions(-) create mode 100644 src/server/src/main/java/io/cassandrareaper/core/Table.java create mode 100644 src/server/src/test/java/io/cassandrareaper/service/RepairUnitServiceTest.java diff --git a/src/docs/content/docs/configuration/reaper_specific.md b/src/docs/content/docs/configuration/reaper_specific.md index 6a66eee92..1d36469f1 100644 --- a/src/docs/content/docs/configuration/reaper_specific.md +++ b/src/docs/content/docs/configuration/reaper_specific.md @@ -135,6 +135,18 @@ Sets the default repair type unless specifically defined for each run. Note that
+### `blacklistTwcsTables` + +Type: *Boolean* + +Default: *false* + +Disables repairs of any tables that use either the `TimeWindowCompactionStrategy` or `DateTieredCompactionStrategy`. This automatic blacklisting is not stored in schedules or repairs. It is applied when repairs are triggered and visible in the UI for running repairs. Not storing which tables are TWCS/DTCS ensures changes to a table's compaction strategy are honored on every new repair. + +*Note*: It is recommended to enable this option as repairing these tables, when they contain TTL'd data, causes overlaps between partitions across the configured time windows the sstables reside in. This leads to an increased disk usage as the older sstables are unable to be expired despite only containing TTL's data. Repairing DTCS tables has additional issues and is generally not recommended. + +
+ ### `jmxAuth` Optional setting to allow Reaper to establish JMX connections to Cassandra clusters using password based JMX authentication. diff --git a/src/packaging/resource/cassandra-reaper-cassandra-ssl.yaml b/src/packaging/resource/cassandra-reaper-cassandra-ssl.yaml index 09896d422..4142bff47 100644 --- a/src/packaging/resource/cassandra-reaper-cassandra-ssl.yaml +++ b/src/packaging/resource/cassandra-reaper-cassandra-ssl.yaml @@ -1,5 +1,5 @@ # Copyright 2015-2017 Spotify AB -# Copyright 2016-2018 The Last Pickle Ltd +# Copyright 2016-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ hangingRepairTimeoutMins: 30 storageType: cassandra enableCrossOrigin: true incrementalRepair: false +blacklistTwcsTables: false repairManagerSchedulingIntervalSeconds: 10 activateQueryLogger: false jmxConnectionTimeoutInSeconds: 5 diff --git a/src/packaging/resource/cassandra-reaper-cassandra.yaml b/src/packaging/resource/cassandra-reaper-cassandra.yaml index f39b76307..aab6e47c6 100644 --- a/src/packaging/resource/cassandra-reaper-cassandra.yaml +++ b/src/packaging/resource/cassandra-reaper-cassandra.yaml @@ -1,5 +1,5 @@ # Copyright 2015-2017 Spotify AB -# Copyright 2016-2018 The Last Pickle Ltd +# Copyright 2016-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ hangingRepairTimeoutMins: 30 storageType: cassandra enableCrossOrigin: true incrementalRepair: false +blacklistTwcsTables: false enableDynamicSeedList: true repairManagerSchedulingIntervalSeconds: 10 activateQueryLogger: false diff --git a/src/packaging/resource/cassandra-reaper-h2.yaml b/src/packaging/resource/cassandra-reaper-h2.yaml index aea1c8ce4..85e8f43de 100644 --- a/src/packaging/resource/cassandra-reaper-h2.yaml +++ b/src/packaging/resource/cassandra-reaper-h2.yaml @@ -1,5 +1,5 @@ # Copyright 2015-2017 Spotify AB -# Copyright 2016-2018 The Last Pickle Ltd +# Copyright 2016-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ hangingRepairTimeoutMins: 30 storageType: h2 enableCrossOrigin: true incrementalRepair: false +blacklistTwcsTables: false enableDynamicSeedList: true repairManagerSchedulingIntervalSeconds: 10 jmxConnectionTimeoutInSeconds: 5 diff --git a/src/packaging/resource/cassandra-reaper-memory.yaml b/src/packaging/resource/cassandra-reaper-memory.yaml index b2f0d8497..19c53f584 100644 --- a/src/packaging/resource/cassandra-reaper-memory.yaml +++ b/src/packaging/resource/cassandra-reaper-memory.yaml @@ -1,5 +1,5 @@ # Copyright 2015-2017 Spotify AB -# Copyright 2016-2018 The Last Pickle Ltd +# Copyright 2016-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ hangingRepairTimeoutMins: 30 storageType: memory enableCrossOrigin: true incrementalRepair: false +blacklistTwcsTables: false enableDynamicSeedList: true repairManagerSchedulingIntervalSeconds: 10 jmxConnectionTimeoutInSeconds: 5 diff --git a/src/packaging/resource/cassandra-reaper-postgres.yaml b/src/packaging/resource/cassandra-reaper-postgres.yaml index 907210e95..2fb6ccb9b 100644 --- a/src/packaging/resource/cassandra-reaper-postgres.yaml +++ b/src/packaging/resource/cassandra-reaper-postgres.yaml @@ -1,5 +1,5 @@ # Copyright 2015-2017 Spotify AB -# Copyright 2016-2018 The Last Pickle Ltd +# Copyright 2016-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ hangingRepairTimeoutMins: 30 storageType: postgres enableCrossOrigin: true incrementalRepair: false +blacklistTwcsTables: false enableDynamicSeedList: true repairManagerSchedulingIntervalSeconds: 10 jmxConnectionTimeoutInSeconds: 5 diff --git a/src/packaging/resource/cassandra-reaper.yaml b/src/packaging/resource/cassandra-reaper.yaml index afaf35efd..ada0efc13 100644 --- a/src/packaging/resource/cassandra-reaper.yaml +++ b/src/packaging/resource/cassandra-reaper.yaml @@ -1,5 +1,5 @@ # Copyright 2015-2017 Spotify AB -# Copyright 2016-2018 The Last Pickle Ltd +# Copyright 2016-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ hangingRepairTimeoutMins: 30 storageType: memory enableCrossOrigin: true incrementalRepair: false +blacklistTwcsTables: false enableDynamicSeedList: true repairManagerSchedulingIntervalSeconds: 10 activateQueryLogger: false diff --git a/src/server/src/main/java/io/cassandrareaper/ReaperApplicationConfiguration.java b/src/server/src/main/java/io/cassandrareaper/ReaperApplicationConfiguration.java index 980211d52..f86646016 100644 --- a/src/server/src/main/java/io/cassandrareaper/ReaperApplicationConfiguration.java +++ b/src/server/src/main/java/io/cassandrareaper/ReaperApplicationConfiguration.java @@ -1,6 +1,6 @@ /* * Copyright 2014-2017 Spotify AB - * Copyright 2016-2018 The Last Pickle Ltd + * Copyright 2016-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,6 +65,11 @@ public final class ReaperApplicationConfiguration extends Configuration { @DefaultValue("false") private Boolean incrementalRepair; + @JsonProperty + @NotNull + @DefaultValue("false") + private Boolean blacklistTwcsTables; + @DefaultValue("7") private Integer scheduleDaysBetween; @@ -185,6 +190,14 @@ public void setIncrementalRepair(boolean incrementalRepair) { this.incrementalRepair = incrementalRepair; } + public boolean getBlacklistTwcsTables() { + return blacklistTwcsTables != null ? blacklistTwcsTables : false; + } + + public void setBlacklistTwcsTables(boolean blacklistTwcsTables) { + this.blacklistTwcsTables = blacklistTwcsTables; + } + public Integer getScheduleDaysBetween() { return scheduleDaysBetween != null ? scheduleDaysBetween : 7; } diff --git a/src/server/src/main/java/io/cassandrareaper/core/Table.java b/src/server/src/main/java/io/cassandrareaper/core/Table.java new file mode 100644 index 000000000..612e71e36 --- /dev/null +++ b/src/server/src/main/java/io/cassandrareaper/core/Table.java @@ -0,0 +1,77 @@ +/* + * Copyright 2018-2019 The Last Pickle Ltd + * + * + * 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 io.cassandrareaper.core; + +import com.google.common.base.Preconditions; + +public final class Table { + + private final String name; + private final String compactionStrategy; + + private Table(Builder builder) { + this.name = builder.name; + this.compactionStrategy = builder.compactionStrategy; + } + + public String getName() { + return name; + } + + public String getCompactionStrategy() { + return compactionStrategy; + } + + public static Builder builder() { + return new Builder(); + } + + public String toString() { + return String.format("{name=%s, compactionStrategy=%s}", name, compactionStrategy); + } + + public static final class Builder { + + private String name; + private String compactionStrategy; + + private Builder() { + } + + public Table.Builder withName(String name) { + Preconditions.checkState(null == this.name, "`.withName(..)` can only be called once"); + this.name = name; + return this; + } + + public Table.Builder withCompactionStrategy(String compactionStrategy) { + Preconditions + .checkState(null == this.compactionStrategy, "`.withCompactionStrategy(..)` can only be called once"); + + this.compactionStrategy = compactionStrategy; + return this; + } + + public Table build() { + Preconditions.checkNotNull(name, "`.withName(..)` must be called before `.build()`"); + Preconditions.checkNotNull(compactionStrategy, "`.withCompactionStrategy(..)` must be called before `.build()`"); + return new Table(this); + } + } + +} diff --git a/src/server/src/main/java/io/cassandrareaper/jmx/JmxProxy.java b/src/server/src/main/java/io/cassandrareaper/jmx/JmxProxy.java index 026b107f8..d128e8b2c 100644 --- a/src/server/src/main/java/io/cassandrareaper/jmx/JmxProxy.java +++ b/src/server/src/main/java/io/cassandrareaper/jmx/JmxProxy.java @@ -1,6 +1,6 @@ /* * Copyright 2014-2017 Spotify AB - * Copyright 2016-2018 The Last Pickle Ltd + * Copyright 2016-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import io.cassandrareaper.ReaperException; import io.cassandrareaper.core.Segment; +import io.cassandrareaper.core.Table; import io.cassandrareaper.service.RingRange; import java.math.BigInteger; @@ -80,7 +81,7 @@ public interface JmxProxy extends NotificationListener { List getRangesForLocalEndpoint(String keyspace) throws ReaperException; - Set getTableNamesForKeyspace(String keyspace) throws ReaperException; + Set getTablesForKeyspace(String keyspace) throws ReaperException; /** * @return list of tokens in the cluster diff --git a/src/server/src/main/java/io/cassandrareaper/jmx/JmxProxyImpl.java b/src/server/src/main/java/io/cassandrareaper/jmx/JmxProxyImpl.java index 342a4ac4a..481cc84cc 100644 --- a/src/server/src/main/java/io/cassandrareaper/jmx/JmxProxyImpl.java +++ b/src/server/src/main/java/io/cassandrareaper/jmx/JmxProxyImpl.java @@ -1,6 +1,6 @@ /* * Copyright 2014-2017 Spotify AB - * Copyright 2016-2018 The Last Pickle Ltd + * Copyright 2016-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import io.cassandrareaper.ReaperException; import io.cassandrareaper.core.Cluster; import io.cassandrareaper.core.Segment; +import io.cassandrareaper.core.Table; import io.cassandrareaper.service.RingRange; import java.io.IOException; @@ -387,8 +388,8 @@ public List getKeyspaces() { } @Override - public Set getTableNamesForKeyspace(String keyspace) throws ReaperException { - Set tableNames = new HashSet<>(); + public Set
getTablesForKeyspace(String keyspace) throws ReaperException { + Set
tables = new HashSet<>(); Iterator> proxies; try { proxies = ColumnFamilyStoreMBeanIterator.getColumnFamilyStoreMBeanProxies(mbeanServer); @@ -400,10 +401,13 @@ public Set getTableNamesForKeyspace(String keyspace) throws ReaperExcept String keyspaceName = proxyEntry.getKey(); if (keyspace.equalsIgnoreCase(keyspaceName)) { ColumnFamilyStoreMBean columnFamilyMBean = proxyEntry.getValue(); - tableNames.add(columnFamilyMBean.getColumnFamilyName()); + tables.add(Table.builder() + .withName(columnFamilyMBean.getColumnFamilyName()) + .withCompactionStrategy(columnFamilyMBean.getCompactionParameters().get("class")) + .build()); } } - return tableNames; + return tables; } @Override diff --git a/src/server/src/main/java/io/cassandrareaper/resources/RepairRunResource.java b/src/server/src/main/java/io/cassandrareaper/resources/RepairRunResource.java index 0a01feb78..b43e9e073 100644 --- a/src/server/src/main/java/io/cassandrareaper/resources/RepairRunResource.java +++ b/src/server/src/main/java/io/cassandrareaper/resources/RepairRunResource.java @@ -1,6 +1,6 @@ /* * Copyright 2014-2017 Spotify AB - * Copyright 2016-2018 The Last Pickle Ltd + * Copyright 2016-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -113,17 +113,20 @@ public Response addRepairRun( try { final Response possibleFailedResponse = RepairRunResource.checkRequestForAddRepair( - context, - clusterName, - keyspace, - owner, - segmentCountPerNode, - repairParallelism, - intensityStr, - incrementalRepairStr, - nodesToRepairParam, - datacentersToRepairParam, - repairThreadCountParam); + context, + clusterName, + keyspace, + tableNamesParam, + owner, + segmentCountPerNode, + repairParallelism, + intensityStr, + incrementalRepairStr, + nodesToRepairParam, + datacentersToRepairParam, + blacklistedTableNamesParam, + repairThreadCountParam); + if (null != possibleFailedResponse) { return possibleFailedResponse; } @@ -205,7 +208,7 @@ public Response addRepairRun( .blacklistedTables(blacklistedTableNames) .repairThreadCount(repairThreadCountParam.orElse(context.config.getRepairThreadCount())); - RepairUnit theRepairUnit = repairUnitService.getOrCreateRepairUnit(cluster, builder); + final RepairUnit theRepairUnit = repairUnitService.getOrCreateRepairUnit(cluster, builder); if (theRepairUnit.getIncrementalRepair() != incrementalRepair) { String msg = String.format( @@ -243,7 +246,7 @@ public Response addRepairRun( intensity); return Response.created(buildRepairRunUri(uriInfo, newRepairRun)) - .entity(new RepairRunStatus(newRepairRun, theRepairUnit, 0)) + .entity(new RepairRunStatus(newRepairRun, addAnyTwcsBlacklistedTables(theRepairUnit), 0)) .build(); } catch (ReaperException e) { @@ -261,6 +264,7 @@ static Response checkRequestForAddRepair( AppContext context, Optional clusterName, Optional keyspace, + Optional tableNamesParam, Optional owner, Optional segmentCountPerNode, Optional repairParallelism, @@ -268,8 +272,8 @@ static Response checkRequestForAddRepair( Optional incrementalRepairStr, Optional nodesStr, Optional datacentersStr, - Optional repairThreadCountStr) - throws ReaperException { + Optional blacklistedTableNamesParam, + Optional repairThreadCountStr) throws ReaperException { if (!clusterName.isPresent()) { return createMissingArgumentResponse("clusterName"); @@ -346,6 +350,12 @@ static Response checkRequestForAddRepair( } } + if (tableNamesParam.isPresent() && blacklistedTableNamesParam.isPresent()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("invalid to specify a table list and a blacklist") + .build(); + } + return null; } @@ -622,7 +632,7 @@ public Response getRepairRunsForCluster( private RepairRunStatus getRepairRunStatus(RepairRun repairRun) { RepairUnit repairUnit = context.storage.getRepairUnit(repairRun.getRepairUnitId()); int segmentsRepaired = getSegmentAmountForRepairRun(repairRun.getId()); - return new RepairRunStatus(repairRun, repairUnit, segmentsRepaired); + return new RepairRunStatus(repairRun, addAnyTwcsBlacklistedTables(repairUnit), segmentsRepaired); } /** @@ -695,7 +705,7 @@ private List getRunStatuses( if (!run.getRunState().equals(RepairRun.RunState.DONE)) { segmentsRepaired = getSegmentAmountForRepairRun(run.getId()); } - runStatuses.add(new RepairRunStatus(run, runsUnit, segmentsRepaired)); + runStatuses.add(new RepairRunStatus(run, addAnyTwcsBlacklistedTables(runsUnit), segmentsRepaired)); } return runStatuses; @@ -773,6 +783,22 @@ public Response purgeRepairRuns() throws ReaperException { return Response.ok().entity(purgedRepairs).build(); } + private RepairUnit addAnyTwcsBlacklistedTables(RepairUnit unit) { + if (unit.getColumnFamilies().isEmpty()) { + // modify the RepairUnit to show in the UI any twcs blacklisted tables + try { + Cluster cluster = context.storage.getCluster(unit.getClusterName()).get(); + + Set twcsTables = Sets.newHashSet( + repairUnitService.findBlacklistedCompactionStrategyTables(cluster, unit.getKeyspaceName())); + + return unit.with().blacklistedTables(Sets.union(unit.getBlacklistedTables(), twcsTables)).build(unit.getId()); + } catch (ReaperException e) { + LOG.error(e.getMessage(), e); + } + } + return unit; + } private static void checkRepairParallelismString(String repairParallelism) throws ReaperException { try { diff --git a/src/server/src/main/java/io/cassandrareaper/resources/RepairScheduleResource.java b/src/server/src/main/java/io/cassandrareaper/resources/RepairScheduleResource.java index dad8cb8d0..7df7c0fc4 100644 --- a/src/server/src/main/java/io/cassandrareaper/resources/RepairScheduleResource.java +++ b/src/server/src/main/java/io/cassandrareaper/resources/RepairScheduleResource.java @@ -105,17 +105,19 @@ public Response addRepairSchedule( try { Response possibleFailResponse = RepairRunResource.checkRequestForAddRepair( - context, - clusterName, - keyspace, - owner, - segmentCountPerNode, - repairParallelism, - intensityStr, - incrementalRepairStr, - nodesToRepairParam, - datacentersToRepairParam, - repairThreadCountParam); + context, + clusterName, + keyspace, + tableNamesParam, + owner, + segmentCountPerNode, + repairParallelism, + intensityStr, + incrementalRepairStr, + nodesToRepairParam, + datacentersToRepairParam, + blacklistedTableNamesParam, + repairThreadCountParam); if (null != possibleFailResponse) { return possibleFailResponse; diff --git a/src/server/src/main/java/io/cassandrareaper/service/ClusterRepairScheduler.java b/src/server/src/main/java/io/cassandrareaper/service/ClusterRepairScheduler.java index 244d75c39..13895851b 100644 --- a/src/server/src/main/java/io/cassandrareaper/service/ClusterRepairScheduler.java +++ b/src/server/src/main/java/io/cassandrareaper/service/ClusterRepairScheduler.java @@ -1,6 +1,6 @@ /* * Copyright 2017-2017 Spotify AB - * Copyright 2017-2018 The Last Pickle Ltd + * Copyright 2017-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import com.google.common.base.Throwables; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import org.joda.time.DateTime; @@ -96,7 +95,7 @@ private boolean keyspaceCandidateForRepair(Cluster cluster, String keyspace) { LOG.debug("Scheduled repair skipped for system keyspace {} in cluster {}.", keyspace, cluster.getName()); return false; } - if (keyspaceHasNoTable(context, cluster, keyspace)) { + if (repairUnitService.getTableNamesForKeyspace(cluster, keyspace).isEmpty()) { LOG.warn( "No tables found for keyspace {} in cluster {}. No repair will be scheduled for this keyspace.", keyspace, @@ -128,18 +127,6 @@ private void createRepairSchedule(Cluster cluster, String keyspace, DateTime nex LOG.info("Scheduled repair created: {}", repairSchedule); } - private boolean keyspaceHasNoTable(AppContext context, Cluster cluster, String keyspace) { - try { - JmxProxy jmxProxy = context.jmxConnectionFactory.connectAny( - cluster, context.config.getJmxConnectionTimeoutInSeconds()); - - Set tables = jmxProxy.getTableNamesForKeyspace(keyspace); - return tables.isEmpty(); - } catch (ReaperException e) { - throw Throwables.propagate(e); - } - } - private static class ScheduledRepairDiffView { private final ImmutableSet keyspacesThatRequireSchedules; diff --git a/src/server/src/main/java/io/cassandrareaper/service/RepairRunService.java b/src/server/src/main/java/io/cassandrareaper/service/RepairRunService.java index 8803343e9..91eab1187 100644 --- a/src/server/src/main/java/io/cassandrareaper/service/RepairRunService.java +++ b/src/server/src/main/java/io/cassandrareaper/service/RepairRunService.java @@ -1,6 +1,6 @@ /* * Copyright 2015-2017 Spotify AB - * Copyright 2016-2018 The Last Pickle Ltd + * Copyright 2016-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import io.cassandrareaper.core.RepairSegment; import io.cassandrareaper.core.RepairUnit; import io.cassandrareaper.core.Segment; +import io.cassandrareaper.core.Table; import io.cassandrareaper.jmx.JmxProxy; import java.math.BigInteger; @@ -348,7 +349,7 @@ public Set getTableNamesBasedOnParam( JmxProxy jmxProxy = context.jmxConnectionFactory.connectAny( cluster, context.config.getJmxConnectionTimeoutInSeconds()); - knownTables = jmxProxy.getTableNamesForKeyspace(keyspace); + knownTables = jmxProxy.getTablesForKeyspace(keyspace).stream().map(Table::getName).collect(Collectors.toSet()); if (knownTables.isEmpty()) { LOG.debug("no known tables for keyspace {} in cluster {}", keyspace, cluster.getName()); throw new IllegalArgumentException("no column families found for keyspace"); diff --git a/src/server/src/main/java/io/cassandrareaper/service/RepairUnitService.java b/src/server/src/main/java/io/cassandrareaper/service/RepairUnitService.java index 6f3ca3f65..1c42b98ca 100644 --- a/src/server/src/main/java/io/cassandrareaper/service/RepairUnitService.java +++ b/src/server/src/main/java/io/cassandrareaper/service/RepairUnitService.java @@ -1,6 +1,6 @@ /* * Copyright 2017-2017 Spotify AB - * Copyright 2017-2018 The Last Pickle Ltd + * Copyright 2017-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,17 @@ import io.cassandrareaper.core.Cluster; import io.cassandrareaper.core.RepairSchedule; import io.cassandrareaper.core.RepairUnit; +import io.cassandrareaper.core.Table; import io.cassandrareaper.jmx.JmxProxy; import java.util.Collection; import java.util.Collections; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +42,10 @@ public final class RepairUnitService { private static final Logger LOG = LoggerFactory.getLogger(RepairUnitService.class); + + private static final Set BLACKLISTED_STRATEGEIS + = ImmutableSet.of("TimeWindowCompactionStrategy", "DateTieredCompactionStrategy"); + private final AppContext context; private RepairUnitService(AppContext context) { @@ -67,6 +74,64 @@ public RepairUnit getOrCreateRepairUnit(Cluster cluster, RepairUnit.Builder para return repairUnit.isPresent() ? repairUnit.get() : createRepairUnit(cluster, params); } + /** + * Applies blacklist filter on tables for the given repair unit. + * + * @param proxy : a JMX proxy instance + * @param unit : the repair unit for the current run + * @return the list of tables to repair for the keyspace without the blacklisted ones + * @throws ReaperException, IllegalStateException + */ + Set getTablesToRepair(JmxProxy proxy, Cluster cluster, RepairUnit repairUnit) + throws ReaperException, IllegalStateException { + + String keyspace = repairUnit.getKeyspaceName(); + Set tables; + + if (repairUnit.getColumnFamilies().isEmpty()) { + Set twcsBlacklisted = findBlacklistedCompactionStrategyTables(cluster, keyspace); + + tables = proxy.getTablesForKeyspace(keyspace).stream() + .map(Table::getName) + .filter(tableName -> !repairUnit.getBlacklistedTables().contains(tableName)) + .filter(tableName -> !twcsBlacklisted.contains(tableName)) + .collect(Collectors.toSet()); + } else { + // if tables have been specified then don't apply the twcsBlacklisting + tables = repairUnit.getColumnFamilies().stream() + .filter(tableName -> !repairUnit.getBlacklistedTables().contains(tableName)) + .collect(Collectors.toSet()); + } + + Preconditions.checkState( + repairUnit.getBlacklistedTables().isEmpty() || !tables.isEmpty(), + "Invalid blacklist definition. It filtered out all tables in the keyspace."); + + return tables; + } + + public Set findBlacklistedCompactionStrategyTables(Cluster cluster, String keyspace) { + if (context.config.getBlacklistTwcsTables()) { + try { + return context.jmxConnectionFactory.connectAny(cluster, context.config.getJmxConnectionTimeoutInSeconds()) + .getTablesForKeyspace(keyspace) + .stream() + .filter(RepairUnitService::isBlackListedCompactionStrategy) + .map(Table::getName) + .collect(Collectors.toSet()); + + } catch (ReaperException e) { + LOG.error("unknown table list to cluster {} keyspace", cluster.getName(), keyspace, e); + } + } + return Collections.emptySet(); + } + + private static boolean isBlackListedCompactionStrategy(Table table) { + return BLACKLISTED_STRATEGEIS.stream() + .anyMatch(s -> table.getCompactionStrategy().toLowerCase().contains(s.toLowerCase())); + } + private RepairUnit createRepairUnit(Cluster cluster, RepairUnit.Builder builder) { Preconditions.checkArgument( !unitConflicts(cluster, builder), @@ -107,12 +172,11 @@ boolean conflictingUnits(Cluster cluster, RepairUnit unit, RepairUnit.Builder bu return !Sets.intersection(listRepairTables(unit.with(), tables), listRepairTables(builder, tables)).isEmpty(); } - private Set getTableNamesForKeyspace(Cluster cluster, String keyspace) { + public Set getTableNamesForKeyspace(Cluster cluster, String keyspace) { try { return context .jmxConnectionFactory.connectAny(cluster, context.config.getJmxConnectionTimeoutInSeconds()) - .getTableNamesForKeyspace(keyspace); - + .getTablesForKeyspace(keyspace).stream().map(Table::getName).collect(Collectors.toSet()); } catch (ReaperException e) { LOG.warn("unknown table list to cluster {} keyspace", cluster.getName(), keyspace, e); return Collections.emptySet(); diff --git a/src/server/src/main/java/io/cassandrareaper/service/SegmentRunner.java b/src/server/src/main/java/io/cassandrareaper/service/SegmentRunner.java index 8d4b025db..aca7cfbc8 100644 --- a/src/server/src/main/java/io/cassandrareaper/service/SegmentRunner.java +++ b/src/server/src/main/java/io/cassandrareaper/service/SegmentRunner.java @@ -1,6 +1,6 @@ /* * Copyright 2015-2017 Spotify AB - * Copyright 2016-2018 The Last Pickle Ltd + * Copyright 2016-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,6 +89,7 @@ final class SegmentRunner implements RepairStatusHandler, Runnable { private static final long METRICS_MAX_WAIT_MS = TimeUnit.MINUTES.toMillis(2); private final AppContext context; + private final RepairUnitService repairUnitService; private final UUID segmentId; private final Condition condition = new SimpleCondition(); private final Collection potentialCoordinators; @@ -122,6 +123,7 @@ final class SegmentRunner implements RepairStatusHandler, Runnable { throw new ReaperException("SegmentRunner already exists for segment with ID: " + segmentId); } this.context = context; + this.repairUnitService = RepairUnitService.create(context); this.segmentId = segmentId; this.potentialCoordinators = potentialCoordinators; this.timeoutMillis = timeoutMillis; @@ -264,7 +266,7 @@ private boolean runRepair() { try (Timer.Context cxt1 = context.metricRegistry.timer(metricNameForRepairing(segment)).time()) { Set tablesToRepair; try { - tablesToRepair = getTablesToRepair(coordinator, repairUnit); + tablesToRepair = repairUnitService.getTablesToRepair(coordinator, cluster, repairUnit); } catch (IllegalStateException e) { String msg = "Invalid blacklist definition. It filtered all tables in the keyspace."; LOG.error(msg, e); @@ -1154,40 +1156,6 @@ private int countRunningReapers() { : 1; } - /** - * Applies blacklist filter on tables for the given repair unit. - * - * @param coordinator : a JMX proxy instance - * @param unit : the repair unit for the current run - * @return the list of tables to repair for the keyspace without the blacklisted ones - * @throws ReaperException, IllegalStateException - */ - static Set getTablesToRepair(JmxProxy coordinator, RepairUnit unit) - throws ReaperException, IllegalStateException { - Set tables = unit.getColumnFamilies(); - - if (!unit.getBlacklistedTables().isEmpty() && unit.getColumnFamilies().isEmpty()) { - tables = coordinator - .getTableNamesForKeyspace(unit.getKeyspaceName()) - .stream() - .filter(tableName -> !unit.getBlacklistedTables().contains(tableName)) - .collect(Collectors.toSet()); - } - - if (!unit.getBlacklistedTables().isEmpty() && !unit.getColumnFamilies().isEmpty()) { - tables = unit.getColumnFamilies() - .stream() - .filter(tableName -> !unit.getBlacklistedTables().contains(tableName)) - .collect(Collectors.toSet()); - } - - Preconditions.checkState( - !(!unit.getBlacklistedTables().isEmpty() - && tables.isEmpty())); // if we have a blacklist, we should have tables in the output. - - return tables; - } - private class BusyHostsInitializer extends LazyInitializer> { private final JmxProxy coordinator; diff --git a/src/server/src/test/java/io/cassandrareaper/ReaperApplicationConfigurationTest.java b/src/server/src/test/java/io/cassandrareaper/ReaperApplicationConfigurationTest.java index b98645597..ad7efd4cd 100644 --- a/src/server/src/test/java/io/cassandrareaper/ReaperApplicationConfigurationTest.java +++ b/src/server/src/test/java/io/cassandrareaper/ReaperApplicationConfigurationTest.java @@ -1,6 +1,6 @@ /* * Copyright 2015-2017 Spotify AB - * Copyright 2016-2018 The Last Pickle Ltd + * Copyright 2016-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,7 @@ public void setUp() { config.setScheduleDaysBetween(7); config.setStorageType("foo"); config.setIncrementalRepair(false); + config.setBlacklistTwcsTables(true); } @Test diff --git a/src/server/src/test/java/io/cassandrareaper/acceptance/BasicSteps.java b/src/server/src/test/java/io/cassandrareaper/acceptance/BasicSteps.java index 9b3b4794e..9ccc50595 100644 --- a/src/server/src/test/java/io/cassandrareaper/acceptance/BasicSteps.java +++ b/src/server/src/test/java/io/cassandrareaper/acceptance/BasicSteps.java @@ -48,6 +48,7 @@ import com.datastax.driver.core.Host; import com.datastax.driver.core.Session; import com.datastax.driver.core.SocketOptions; +import com.datastax.driver.core.VersionNumber; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; @@ -874,6 +875,26 @@ public void a_new_repair_is_added_for_and_keyspace_with_blacklisted_table( } } + @When( + "^a new repair is added for the last added cluster " + + "and keyspace \"([^\"]*)\" for tables \"([^\"]*)\"$") + public void a_new_repair_is_added_for_and_keyspace_for_tables( + String keyspace, String tables) throws Throwable { + synchronized (BasicSteps.class) { + ReaperTestJettyRunner runner = RUNNERS.get(RAND.nextInt(RUNNERS.size())); + Map params = Maps.newHashMap(); + params.put("clusterName", TestContext.TEST_CLUSTER); + params.put("keyspace", keyspace); + params.put("owner", TestContext.TEST_USER); + params.put("tables", tables); + Response response = runner.callReaper("POST", "/repair_run", Optional.of(params)); + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + String responseData = response.readEntity(String.class); + RepairRunStatus run = SimpleReaperClient.parseRepairRunStatusJSON(responseData); + TestContext.LAST_MODIFIED_ID = run.getId(); + } + } + @When("^a new repair is added for the last added cluster and keyspace \"([^\"]*)\"$") public void a_new_repair_is_added_for_the_last_added_cluster_and_keyspace(String keyspace) throws Throwable { synchronized (BasicSteps.class) { @@ -908,6 +929,24 @@ public void the_last_added_repair_has_table_in_the_blacklist(String blacklistedT } } + @And("^the last added repair has table \"([^\"]*)\" in the table list$") + public void the_last_added_repair_has_table_in_the_table_list(String blacklistedTable) + throws Throwable { + synchronized (BasicSteps.class) { + RUNNERS + .parallelStream() + .forEach( + runner -> { + Response response = runner.callReaper( + "GET", "/repair_run/cluster/" + TestContext.TEST_CLUSTER, EMPTY_PARAMS); + String responseData = response.readEntity(String.class); + assertEquals(responseData, Response.Status.OK.getStatusCode(), response.getStatus()); + List runs = SimpleReaperClient.parseRepairRunStatusListJSON(responseData); + assertTrue(runs.get(0).getColumnFamilies().contains(blacklistedTable)); + }); + } + } + @When("^a new incremental repair is added for \"([^\"]*)\" and keyspace \"([^\"]*)\"$") public void a_new_incremental_repair_is_added_for_and_keyspace(String clusterName, String keyspace) throws Throwable { synchronized (BasicSteps.class) { @@ -1601,8 +1640,38 @@ private static Cluster buildCluster() { private static void createTable(String keyspaceName, String tableName) { try (Cluster cluster = buildCluster(); Session tmpSession = cluster.connect()) { - tmpSession.execute( - "CREATE TABLE IF NOT EXISTS " + keyspaceName + "." + tableName + "(id int PRIMARY KEY, value text)"); + String createTableStmt + = "CREATE TABLE IF NOT EXISTS " + + keyspaceName + + "." + + tableName + + "(id int PRIMARY KEY, value text)"; + VersionNumber lowestNodeVersion + = tmpSession + .getCluster() + .getMetadata() + .getAllHosts() + .stream() + .map(host -> host.getCassandraVersion()) + .min(VersionNumber::compareTo) + .get(); + + if (tableName.endsWith("twcs")) { + if (((VersionNumber.parse("3.0.8").compareTo(lowestNodeVersion) <= 0 + && VersionNumber.parse("3.0.99").compareTo(lowestNodeVersion) >= 0) + || VersionNumber.parse("3.8").compareTo(lowestNodeVersion) <= 0)) { + // TWCS is available by default + createTableStmt + += " WITH compaction = {'class':'TimeWindowCompactionStrategy'," + + "'compaction_window_size': '1', " + + "'compaction_window_unit': 'MINUTES'}"; + } else { + createTableStmt + += " WITH compaction = {'class':'DateTieredCompactionStrategy'}"; + } + } + + tmpSession.execute(createTableStmt); for (int i = 0; i < 100; i++) { tmpSession.execute( diff --git a/src/server/src/test/java/io/cassandrareaper/resources/ClusterResourceTest.java b/src/server/src/test/java/io/cassandrareaper/resources/ClusterResourceTest.java index 59cb2db3b..f174a703d 100644 --- a/src/server/src/test/java/io/cassandrareaper/resources/ClusterResourceTest.java +++ b/src/server/src/test/java/io/cassandrareaper/resources/ClusterResourceTest.java @@ -1,6 +1,6 @@ /* * Copyright 2015-2017 Spotify AB - * Copyright 2016-2018 The Last Pickle Ltd + * Copyright 2016-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import io.cassandrareaper.AppContext; import io.cassandrareaper.ReaperException; import io.cassandrareaper.core.Cluster; +import io.cassandrareaper.core.Table; import io.cassandrareaper.jmx.JmxConnectionFactory; import io.cassandrareaper.jmx.JmxProxy; import io.cassandrareaper.service.TestRepairConfiguration; @@ -59,6 +60,8 @@ public final class ClusterResourceTest { static final String I_DO_EXIST = "i_do_exist"; static final String I_DONT_EXIST = "i_dont_exist"; + private static final String STCS = "SizeTieredCompactionStrategy"; + @Before public void setUp() throws Exception { } @@ -230,8 +233,9 @@ public void addingAClusterAutomaticallySetupSchedulingRepairsWhenEnabled() throw when(mocks.jmxProxy.getLiveNodes()).thenReturn(Arrays.asList(SEED_HOST)); when(mocks.jmxProxy.getKeyspaces()).thenReturn(Lists.newArrayList("keyspace1")); - when(mocks.jmxProxy.getTableNamesForKeyspace("keyspace1")) - .thenReturn(Sets.newHashSet("table1")); + when(mocks.jmxProxy.getTablesForKeyspace("keyspace1")) + .thenReturn(Sets.newHashSet( + Table.builder().withName("table1").withCompactionStrategy(STCS).build())); mocks.context.config = TestRepairConfiguration.defaultConfigBuilder() .withAutoScheduling( diff --git a/src/server/src/test/java/io/cassandrareaper/resources/RepairRunResourceTest.java b/src/server/src/test/java/io/cassandrareaper/resources/RepairRunResourceTest.java index a45ba6ee0..72131006d 100644 --- a/src/server/src/test/java/io/cassandrareaper/resources/RepairRunResourceTest.java +++ b/src/server/src/test/java/io/cassandrareaper/resources/RepairRunResourceTest.java @@ -1,6 +1,6 @@ /* * Copyright 2015-2017 Spotify AB - * Copyright 2016-2018 The Last Pickle Ltd + * Copyright 2016-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import io.cassandrareaper.core.RepairSegment; import io.cassandrareaper.core.RepairUnit; import io.cassandrareaper.core.Segment; +import io.cassandrareaper.core.Table; import io.cassandrareaper.jmx.JmxConnectionFactory; import io.cassandrareaper.jmx.JmxProxy; import io.cassandrareaper.resources.view.RepairRunStatus; @@ -42,6 +43,7 @@ import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; @@ -92,14 +94,16 @@ public final class RepairRunResourceTest { private static final int SEGMENT_CNT = 6; private static final double REPAIR_INTENSITY = 0.5f; private static final RepairParallelism REPAIR_PARALLELISM = RepairParallelism.SEQUENTIAL; + private static final String STCS = "SizeTieredCompactionStrategy"; private static final List TOKENS = Lists.newArrayList( BigInteger.valueOf(0L), BigInteger.valueOf(100L), BigInteger.valueOf(200L)); - AppContext context; - UriInfo uriInfo; + private AppContext context; + private UriInfo uriInfo; + private JmxProxy proxy; @Before public void setUp() throws Exception { @@ -119,17 +123,20 @@ public void setUp() throws Exception { Cluster cluster = new Cluster(CLUSTER_NAME, Optional.of(PARTITIONER), Sets.newHashSet(SEED_HOST)); context.storage.addCluster(cluster); - context.config = mock(ReaperApplicationConfiguration.class); - when(context.config.getSegmentCount()).thenReturn(SEGMENT_CNT); - when(context.config.getRepairIntensity()).thenReturn(REPAIR_INTENSITY); + context.config = new ReaperApplicationConfiguration(); + context.config.setSegmentCount(SEGMENT_CNT); + context.config.setRepairIntensity(REPAIR_INTENSITY); uriInfo = mock(UriInfo.class); when(uriInfo.getBaseUriBuilder()).thenReturn(UriBuilder.fromUri(SAMPLE_URI)); - final JmxProxy proxy = mock(JmxProxy.class); + proxy = mock(JmxProxy.class); when(proxy.getClusterName()).thenReturn(CLUSTER_NAME); when(proxy.getPartitioner()).thenReturn(PARTITIONER); - when(proxy.getTableNamesForKeyspace(KEYSPACE)).thenReturn(TABLES); + when(proxy.getTablesForKeyspace(KEYSPACE)) + .thenReturn(TABLES.stream() + .map(t -> Table.builder().withName(t).withCompactionStrategy(STCS).build()) + .collect(Collectors.toSet())); when(proxy.getEndpointToHostId()).thenReturn(NODES_MAP); when(proxy.getTokens()).thenReturn(TOKENS); when(proxy.isConnectionAlive()).thenReturn(Boolean.TRUE); @@ -202,16 +209,16 @@ private Response addRepairRun( uriInfo, Optional.ofNullable(clusterName), Optional.ofNullable(keyspace), - Optional.of(StringUtils.join(columnFamilies, ',')), + columnFamilies.isEmpty() ? Optional.empty() : Optional.of(StringUtils.join(columnFamilies, ',')), Optional.ofNullable(owner), Optional.ofNullable(cause), Optional.ofNullable(segments), Optional.of(REPAIR_PARALLELISM.name()), Optional.empty(), Optional.empty(), - Optional.of(StringUtils.join(nodes, ',')), + nodes.isEmpty() ? Optional.empty() : Optional.of(StringUtils.join(nodes, ',')), Optional.empty(), - Optional.of(StringUtils.join(blacklistedTables, ',')), + blacklistedTables.isEmpty() ? Optional.empty() : Optional.of(StringUtils.join(blacklistedTables, ',')), Optional.of(repairThreadCount)); } @@ -265,6 +272,92 @@ public void testAddRepairRun() throws Exception { } + @Test + public void doesNotDisplayBlacklistedCompactionStrategies() throws Exception { + context.config.setBlacklistTwcsTables(true); + when(proxy.getKeyspaces()).thenReturn(Lists.newArrayList(KEYSPACE)); + + when(proxy.getTablesForKeyspace(KEYSPACE)).thenReturn( + Sets.newHashSet( + Table.builder().withName("table1") + .withCompactionStrategy("org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy") + .build(), + Table.builder().withName("table2") + .withCompactionStrategy("org.apache.cassandra.db.compaction.DateTieredCompactionStrategy") + .build(), + Table.builder().withName("table3") + .withCompactionStrategy("org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy") + .build())); + + RepairRunResource resource = new RepairRunResource(context); + + Response response = addRepairRun( + resource, + uriInfo, + CLUSTER_NAME, + KEYSPACE, + Collections.EMPTY_SET, + OWNER, + "", + SEGMENT_CNT, + NODES, + BLACKLISTED_TABLES, + REPAIR_THREAD_COUNT); + + assertTrue(response.getEntity().toString(), response.getEntity() instanceof RepairRunStatus); + RepairRunStatus repairRunStatus = (RepairRunStatus) response.getEntity(); + + assertTrue("Blacklisted should contain 'table1'", repairRunStatus.getBlacklistedTables().contains("table1")); + assertTrue("Blacklisted should contain 'table2'", repairRunStatus.getBlacklistedTables().contains("table2")); + assertFalse("Blacklisted shouldn't contain 'table3'", repairRunStatus.getBlacklistedTables().contains("table3")); + assertFalse("ColumnFamilies shouldn't contain 'table1'", repairRunStatus.getColumnFamilies().contains("table1")); + assertFalse("ColumnFamilies shouldn't contain 'table2'", repairRunStatus.getColumnFamilies().contains("table2")); + assertFalse("ColumnFamilies shouldn't contain 'table3'", repairRunStatus.getColumnFamilies().contains("table3")); + } + + @Test + public void displaysExplicitBlacklistedCompactionStrategies() throws Exception { + context.config.setBlacklistTwcsTables(true); + when(proxy.getKeyspaces()).thenReturn(Lists.newArrayList(KEYSPACE)); + + when(proxy.getTablesForKeyspace(KEYSPACE)).thenReturn( + Sets.newHashSet( + Table.builder().withName("table1") + .withCompactionStrategy("org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy") + .build(), + Table.builder().withName("table2") + .withCompactionStrategy("org.apache.cassandra.db.compaction.DateTieredCompactionStrategy") + .build(), + Table.builder().withName("table3") + .withCompactionStrategy("org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy") + .build())); + + RepairRunResource resource = new RepairRunResource(context); + + Response response = addRepairRun( + resource, + uriInfo, + CLUSTER_NAME, + KEYSPACE, + Sets.newHashSet("table1"), + OWNER, + "", + SEGMENT_CNT, + NODES, + BLACKLISTED_TABLES, + REPAIR_THREAD_COUNT); + + assertTrue(response.getEntity().toString(), response.getEntity() instanceof RepairRunStatus); + RepairRunStatus repairRunStatus = (RepairRunStatus) response.getEntity(); + + assertFalse("Blacklisted shouldn't contain 'table1'", repairRunStatus.getBlacklistedTables().contains("table1")); + assertFalse("Blacklisted shouldn't contain 'table2'", repairRunStatus.getBlacklistedTables().contains("table2")); + assertFalse("Blacklisted shouldn't contain 'table3'", repairRunStatus.getBlacklistedTables().contains("table3")); + assertTrue("ColumnFamilies should contain 'table1'", repairRunStatus.getColumnFamilies().contains("table1")); + assertFalse("ColumnFamilies shouldn't contain 'table2'", repairRunStatus.getColumnFamilies().contains("table2")); + assertFalse("ColumnFamilies shouldn't contain 'table3'", repairRunStatus.getColumnFamilies().contains("table3")); + } + @Test public void testTriggerNotExistingRun() throws ReaperException { RepairRunResource resource = new RepairRunResource(context); diff --git a/src/server/src/test/java/io/cassandrareaper/service/ClusterRepairSchedulerTest.java b/src/server/src/test/java/io/cassandrareaper/service/ClusterRepairSchedulerTest.java index e40ee1415..bb1c24184 100644 --- a/src/server/src/test/java/io/cassandrareaper/service/ClusterRepairSchedulerTest.java +++ b/src/server/src/test/java/io/cassandrareaper/service/ClusterRepairSchedulerTest.java @@ -1,6 +1,6 @@ /* * Copyright 2017-2017 Spotify AB - * Copyright 2017-2018 The Last Pickle Ltd + * Copyright 2017-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import io.cassandrareaper.core.Cluster; import io.cassandrareaper.core.RepairSchedule; import io.cassandrareaper.core.RepairUnit; +import io.cassandrareaper.core.Table; import io.cassandrareaper.jmx.JmxConnectionFactory; import io.cassandrareaper.jmx.JmxProxy; import io.cassandrareaper.storage.MemoryStorage; @@ -49,6 +50,8 @@ public final class ClusterRepairSchedulerTest { private static final Cluster CLUSTER = new Cluster("cluster1", null, Collections.singleton("127.0.0.1")); private static final DateTime TWO_HOURS_AGO = DateTime.now().minusHours(2); private static final Duration DELAY_BEFORE_SCHEDULE = Duration.ofMinutes(4); + private static final String STCS = "SizeTieredCompactionStrategy"; + private AppContext context; private ClusterRepairScheduler clusterRepairAuto; private JmxProxy jmxProxy; @@ -78,8 +81,10 @@ public void setup() throws ReaperException { public void schedulesRepairForAllKeyspacesInAllClusters() throws Exception { context.storage.addCluster(CLUSTER); when(jmxProxy.getKeyspaces()).thenReturn(Lists.newArrayList("keyspace1", "keyspace3")); - when(jmxProxy.getTableNamesForKeyspace("keyspace1")).thenReturn(Sets.newHashSet("table1")); - when(jmxProxy.getTableNamesForKeyspace("keyspace3")).thenReturn(Sets.newHashSet("table1")); + when(jmxProxy.getTablesForKeyspace("keyspace1")).thenReturn( + Sets.newHashSet(Table.builder().withName("table1").withCompactionStrategy(STCS).build())); + when(jmxProxy.getTablesForKeyspace("keyspace3")).thenReturn( + Sets.newHashSet(Table.builder().withName("table1").withCompactionStrategy(STCS).build())); clusterRepairAuto.scheduleRepairs(CLUSTER); @@ -119,8 +124,10 @@ public void addSchedulesForNewKeyspace() throws Exception { context.storage.addRepairSchedule(aRepairSchedule(CLUSTER, "keyspace1", TWO_HOURS_AGO)); when(jmxProxy.getKeyspaces()).thenReturn(Lists.newArrayList("keyspace1", "keyspace2")); - when(jmxProxy.getTableNamesForKeyspace("keyspace1")).thenReturn(Sets.newHashSet("table1")); - when(jmxProxy.getTableNamesForKeyspace("keyspace2")).thenReturn(Sets.newHashSet("table2")); + when(jmxProxy.getTablesForKeyspace("keyspace1")).thenReturn( + Sets.newHashSet(Table.builder().withName("table1").withCompactionStrategy(STCS).build())); + when(jmxProxy.getTablesForKeyspace("keyspace2")).thenReturn( + Sets.newHashSet(Table.builder().withName("table2").withCompactionStrategy(STCS).build())); clusterRepairAuto.scheduleRepairs(CLUSTER); @@ -136,7 +143,8 @@ public void addSchedulesForNewKeyspace() throws Exception { public void doesNotScheduleRepairForSystemKeyspaces() throws Exception { context.storage.addCluster(CLUSTER); when(jmxProxy.getKeyspaces()).thenReturn(Lists.newArrayList("system", "system_auth", "system_traces", "keyspace2")); - when(jmxProxy.getTableNamesForKeyspace("keyspace2")).thenReturn(Sets.newHashSet("table1")); + when(jmxProxy.getTablesForKeyspace("keyspace2")).thenReturn( + Sets.newHashSet(Table.builder().withName("table1").withCompactionStrategy(STCS).build())); clusterRepairAuto.scheduleRepairs(CLUSTER); assertThat(context.storage.getAllRepairSchedules()).hasSize(1); @@ -149,7 +157,7 @@ public void doesNotScheduleRepairForSystemKeyspaces() throws Exception { public void doesNotScheduleRepairWhenKeyspaceHasNoTable() throws Exception { context.storage.addCluster(CLUSTER); when(jmxProxy.getKeyspaces()).thenReturn(Lists.newArrayList("keyspace1")); - when(jmxProxy.getTableNamesForKeyspace("keyspace1")).thenReturn(Sets.newHashSet()); + when(jmxProxy.getTablesForKeyspace("keyspace1")).thenReturn(Sets.newHashSet()); clusterRepairAuto.scheduleRepairs(CLUSTER); assertThat(context.storage.getAllRepairSchedules()).hasSize(0); @@ -167,9 +175,12 @@ public void spreadsKeyspaceScheduling() throws Exception { context.storage.addCluster(CLUSTER); when(jmxProxy.getKeyspaces()).thenReturn(Lists.newArrayList("keyspace1", "keyspace2", "keyspace3", "keyspace4")); - when(jmxProxy.getTableNamesForKeyspace("keyspace1")).thenReturn(Sets.newHashSet("sometable")); - when(jmxProxy.getTableNamesForKeyspace("keyspace2")).thenReturn(Sets.newHashSet("sometable")); - when(jmxProxy.getTableNamesForKeyspace("keyspace4")).thenReturn(Sets.newHashSet("sometable")); + when(jmxProxy.getTablesForKeyspace("keyspace1")).thenReturn( + Sets.newHashSet(Table.builder().withName("sometable").withCompactionStrategy(STCS).build())); + when(jmxProxy.getTablesForKeyspace("keyspace2")).thenReturn( + Sets.newHashSet(Table.builder().withName("sometable").withCompactionStrategy(STCS).build())); + when(jmxProxy.getTablesForKeyspace("keyspace4")).thenReturn( + Sets.newHashSet(Table.builder().withName("sometable").withCompactionStrategy(STCS).build())); clusterRepairAuto.scheduleRepairs(CLUSTER); assertThatClusterRepairSchedules(context.storage.getRepairSchedulesForCluster(CLUSTER.getName())) diff --git a/src/server/src/test/java/io/cassandrareaper/service/RepairUnitServiceTest.java b/src/server/src/test/java/io/cassandrareaper/service/RepairUnitServiceTest.java new file mode 100644 index 000000000..61ada181b --- /dev/null +++ b/src/server/src/test/java/io/cassandrareaper/service/RepairUnitServiceTest.java @@ -0,0 +1,262 @@ +/* + * + * Copyright 2019-2019 The Last Pickle Ltd + * + * 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 io.cassandrareaper.service; + + +import io.cassandrareaper.AppContext; +import io.cassandrareaper.ReaperApplicationConfiguration; +import io.cassandrareaper.ReaperException; +import io.cassandrareaper.core.Cluster; +import io.cassandrareaper.core.ClusterProperties; +import io.cassandrareaper.core.RepairUnit; +import io.cassandrareaper.core.Table; +import io.cassandrareaper.jmx.JmxConnectionFactory; +import io.cassandrareaper.jmx.JmxProxy; +import io.cassandrareaper.jmx.JmxProxyTest; + +import java.util.Optional; + +import com.datastax.driver.core.utils.UUIDs; +import com.google.common.collect.Sets; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +public final class RepairUnitServiceTest { + + private static final String STCS = "SizeTieredCompactionStrategy"; + private static final String TWCS = "TimeWindowCompactionStrategy"; + + private AppContext context; + private RepairUnitService service; + + private final Cluster cluster = new Cluster( + "reaper", + Optional.of("murmur3"), + Sets.newHashSet("127.0.0.1"), + ClusterProperties.builder().withJmxPort(7199).build()); + + @Before + public void setUp() throws Exception { + context = new AppContext(); + context.config = new ReaperApplicationConfiguration(); + context.config.setBlacklistTwcsTables(true); + context.jmxConnectionFactory = mock(JmxConnectionFactory.class); + service = RepairUnitService.create(context); + } + + @Test + public void getTablesToRepairRemoveOneTableTest() throws ReaperException { + JmxProxy proxy = JmxProxyTest.mockJmxProxyImpl(); + + when(context.jmxConnectionFactory.connectAny(cluster, context.config.getJmxConnectionTimeoutInSeconds())) + .thenReturn(proxy); + + when(proxy.getTablesForKeyspace(Mockito.anyString())) + .thenReturn(Sets.newHashSet( + Table.builder().withName("table1").withCompactionStrategy(STCS).build(), + Table.builder().withName("table2").withCompactionStrategy(STCS).build(), + Table.builder().withName("table3").withCompactionStrategy(STCS).build())); + + RepairUnit unit = RepairUnit.builder() + .clusterName(cluster.getName()) + .keyspaceName("test") + .blacklistedTables(Sets.newHashSet("table1")) + .incrementalRepair(false) + .repairThreadCount(4) + .build(UUIDs.timeBased()); + + assertEquals(Sets.newHashSet("table2", "table3"), service.getTablesToRepair(proxy, cluster, unit)); + } + + @Test + public void getTablesToRepairRemoveOneTableWithTwcsTest() throws ReaperException { + JmxProxy proxy = JmxProxyTest.mockJmxProxyImpl(); + + when(context.jmxConnectionFactory.connectAny(cluster, context.config.getJmxConnectionTimeoutInSeconds())) + .thenReturn(proxy); + + when(proxy.getTablesForKeyspace(Mockito.anyString())) + .thenReturn(Sets.newHashSet( + Table.builder().withName("table1").withCompactionStrategy(TWCS).build(), + Table.builder().withName("table2").withCompactionStrategy(STCS).build(), + Table.builder().withName("table3").withCompactionStrategy(STCS).build())); + + RepairUnit unit = RepairUnit.builder() + .clusterName(cluster.getName()) + .keyspaceName("test") + .incrementalRepair(false) + .repairThreadCount(4) + .build(UUIDs.timeBased()); + + assertEquals(Sets.newHashSet("table2", "table3"), service.getTablesToRepair(proxy, cluster, unit)); + } + + @Test + public void getTablesToRepairRemoveTwoTablesTest() throws ReaperException { + JmxProxy proxy = JmxProxyTest.mockJmxProxyImpl(); + + when(context.jmxConnectionFactory.connectAny(cluster, context.config.getJmxConnectionTimeoutInSeconds())) + .thenReturn(proxy); + + when(proxy.getTablesForKeyspace(Mockito.anyString())) + .thenReturn(Sets.newHashSet( + Table.builder().withName("table1").withCompactionStrategy(STCS).build(), + Table.builder().withName("table2").withCompactionStrategy(STCS).build(), + Table.builder().withName("table3").withCompactionStrategy(STCS).build())); + + RepairUnit unit = RepairUnit.builder() + .clusterName(cluster.getName()) + .keyspaceName("test") + .blacklistedTables(Sets.newHashSet("table1", "table3")) + .incrementalRepair(false) + .repairThreadCount(4) + .build(UUIDs.timeBased()); + + assertEquals(Sets.newHashSet("table2"), service.getTablesToRepair(proxy, cluster, unit)); + } + + @Test + public void getTablesToRepairRemoveTwoTablesOneWithTwcsTest() throws ReaperException { + JmxProxy proxy = JmxProxyTest.mockJmxProxyImpl(); + + when(context.jmxConnectionFactory.connectAny(cluster, context.config.getJmxConnectionTimeoutInSeconds())) + .thenReturn(proxy); + + when(proxy.getTablesForKeyspace(Mockito.anyString())) + .thenReturn(Sets.newHashSet( + Table.builder().withName("table1").withCompactionStrategy(STCS).build(), + Table.builder().withName("table2").withCompactionStrategy(STCS).build(), + Table.builder().withName("table3").withCompactionStrategy(TWCS).build())); + + RepairUnit unit = RepairUnit.builder() + .clusterName(cluster.getName()) + .keyspaceName("test") + .blacklistedTables(Sets.newHashSet("table1")) + .incrementalRepair(false) + .repairThreadCount(4) + .build(UUIDs.timeBased()); + + assertEquals(Sets.newHashSet("table2"), service.getTablesToRepair(proxy, cluster, unit)); + } + + @Test + public void getTablesToRepairRemoveOneTableFromListTest() throws ReaperException { + JmxProxy proxy = JmxProxyTest.mockJmxProxyImpl(); + + when(context.jmxConnectionFactory.connectAny(cluster, context.config.getJmxConnectionTimeoutInSeconds())) + .thenReturn(proxy); + + when(proxy.getTablesForKeyspace(Mockito.anyString())) + .thenReturn(Sets.newHashSet( + Table.builder().withName("table1").withCompactionStrategy(STCS).build(), + Table.builder().withName("table2").withCompactionStrategy(STCS).build(), + Table.builder().withName("table3").withCompactionStrategy(STCS).build())); + + RepairUnit unit = RepairUnit.builder() + .clusterName(cluster.getName()) + .keyspaceName("test") + .columnFamilies(Sets.newHashSet("table1", "table2")) + .blacklistedTables(Sets.newHashSet("table1")) + .incrementalRepair(false) + .repairThreadCount(4) + .build(UUIDs.timeBased()); + + assertEquals(Sets.newHashSet("table2"), service.getTablesToRepair(proxy, cluster, unit)); + } + + @Test + public void getTablesToRepairRemoveOneTableFromListOneWithTwcsTest() throws ReaperException { + JmxProxy proxy = JmxProxyTest.mockJmxProxyImpl(); + + when(context.jmxConnectionFactory.connectAny(cluster, context.config.getJmxConnectionTimeoutInSeconds())) + .thenReturn(proxy); + + when(proxy.getTablesForKeyspace(Mockito.anyString())) + .thenReturn(Sets.newHashSet( + Table.builder().withName("table1").withCompactionStrategy(STCS).build(), + Table.builder().withName("table2").withCompactionStrategy(TWCS).build(), + Table.builder().withName("table3").withCompactionStrategy(STCS).build())); + + RepairUnit unit = RepairUnit.builder() + .clusterName(cluster.getName()) + .keyspaceName("test") + .columnFamilies(Sets.newHashSet("table1", "table2")) + .blacklistedTables(Sets.newHashSet("table1")) + .incrementalRepair(false) + .repairThreadCount(4) + .build(UUIDs.timeBased()); + + assertEquals(Sets.newHashSet("table2"), service.getTablesToRepair(proxy, cluster, unit)); + } + + @Test(expected = IllegalStateException.class) + public void getTablesToRepairRemoveAllFailingTest() throws ReaperException { + JmxProxy proxy = JmxProxyTest.mockJmxProxyImpl(); + + when(context.jmxConnectionFactory.connectAny(cluster, context.config.getJmxConnectionTimeoutInSeconds())) + .thenReturn(proxy); + + when(proxy.getTablesForKeyspace(Mockito.anyString())) + .thenReturn(Sets.newHashSet( + Table.builder().withName("table1").withCompactionStrategy(STCS).build(), + Table.builder().withName("table2").withCompactionStrategy(STCS).build(), + Table.builder().withName("table3").withCompactionStrategy(STCS).build())); + + RepairUnit unit = RepairUnit.builder() + .clusterName(cluster.getName()) + .keyspaceName("test") + .blacklistedTables(Sets.newHashSet("table1", "table2", "table3")) + .incrementalRepair(false) + .repairThreadCount(4) + .build(UUIDs.timeBased()); + + service.getTablesToRepair(proxy, cluster, unit); + } + + @Test(expected = IllegalStateException.class) + public void getTablesToRepairRemoveAllFromListFailingTest() throws ReaperException { + JmxProxy proxy = JmxProxyTest.mockJmxProxyImpl(); + + when(context.jmxConnectionFactory.connectAny(cluster, context.config.getJmxConnectionTimeoutInSeconds())) + .thenReturn(proxy); + + when(proxy.getTablesForKeyspace(Mockito.anyString())) + .thenReturn(Sets.newHashSet( + Table.builder().withName("table1").withCompactionStrategy(STCS).build(), + Table.builder().withName("table2").withCompactionStrategy(STCS).build(), + Table.builder().withName("table3").withCompactionStrategy(STCS).build(), + Table.builder().withName("table4").withCompactionStrategy(STCS).build())); + + RepairUnit unit = RepairUnit.builder() + .clusterName(cluster.getName()) + .keyspaceName("test") + .columnFamilies(Sets.newHashSet("table1", "table2", "table3")) + .blacklistedTables(Sets.newHashSet("table1", "table2", "table3")) + .incrementalRepair(false) + .repairThreadCount(4) + .build(UUIDs.timeBased()); + + service.getTablesToRepair(proxy, cluster, unit); + } +} diff --git a/src/server/src/test/java/io/cassandrareaper/service/SegmentRunnerTest.java b/src/server/src/test/java/io/cassandrareaper/service/SegmentRunnerTest.java index f18d246df..9b1a2ff3e 100644 --- a/src/server/src/test/java/io/cassandrareaper/service/SegmentRunnerTest.java +++ b/src/server/src/test/java/io/cassandrareaper/service/SegmentRunnerTest.java @@ -1,6 +1,6 @@ /* * Copyright 2015-2017 Spotify AB - * Copyright 2016-2018 The Last Pickle Ltd + * Copyright 2016-2019 The Last Pickle Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1114,77 +1114,6 @@ public void isItOkToRepairTest() { assertTrue(SegmentRunner.okToRepairSegment(true, true, DatacenterAvailability.EACH)); } - @Test - public void getTablesToRepairRemoveOneTableTest() throws ReaperException { - JmxProxy coord = JmxProxyTest.mockJmxProxyImpl(); - when(coord.getTableNamesForKeyspace(Mockito.anyString())) - .thenReturn(Sets.newHashSet("table1", "table2", "table3")); - - - RepairUnit unit = mock(RepairUnit.class); - when(unit.getBlacklistedTables()).thenReturn(Sets.newHashSet("table1")); - when(unit.getColumnFamilies()).thenReturn(Sets.newHashSet()); - when(unit.getKeyspaceName()).thenReturn("test"); - - assertEquals(Sets.newHashSet("table2", "table3"), SegmentRunner.getTablesToRepair(coord, unit)); - } - - @Test - public void getTablesToRepairRemoveTwoTablesTest() throws ReaperException { - JmxProxy coord = JmxProxyTest.mockJmxProxyImpl(); - when(coord.getTableNamesForKeyspace(Mockito.anyString())) - .thenReturn(Sets.newHashSet("table1", "table2", "table3")); - - RepairUnit unit = mock(RepairUnit.class); - when(unit.getBlacklistedTables()).thenReturn(Sets.newHashSet("table1", "table3")); - when(unit.getColumnFamilies()).thenReturn(Sets.newHashSet()); - when(unit.getKeyspaceName()).thenReturn("test"); - - assertEquals(Sets.newHashSet("table2"), SegmentRunner.getTablesToRepair(coord, unit)); - } - - @Test - public void getTablesToRepairRemoveOneTableFromListTest() throws ReaperException { - JmxProxy coord = JmxProxyTest.mockJmxProxyImpl(); - when(coord.getTableNamesForKeyspace(Mockito.anyString())) - .thenReturn(Sets.newHashSet("table1", "table2", "table3")); - - RepairUnit unit = mock(RepairUnit.class); - when(unit.getBlacklistedTables()).thenReturn(Sets.newHashSet("table1")); - when(unit.getColumnFamilies()).thenReturn(Sets.newHashSet("table1", "table2")); - when(unit.getKeyspaceName()).thenReturn("test"); - - assertEquals(Sets.newHashSet("table2"), SegmentRunner.getTablesToRepair(coord, unit)); - } - - @Test(expected = IllegalStateException.class) - public void getTablesToRepairRemoveAllFailingTest() throws ReaperException { - JmxProxy coord = JmxProxyTest.mockJmxProxyImpl(); - when(coord.getTableNamesForKeyspace(Mockito.anyString())) - .thenReturn(Sets.newHashSet("table1", "table2", "table3")); - - RepairUnit unit = mock(RepairUnit.class); - when(unit.getBlacklistedTables()).thenReturn(Sets.newHashSet("table1", "table2", "table3")); - when(unit.getColumnFamilies()).thenReturn(Sets.newHashSet()); - when(unit.getKeyspaceName()).thenReturn("test"); - - SegmentRunner.getTablesToRepair(coord, unit); - } - - @Test(expected = IllegalStateException.class) - public void getTablesToRepairRemoveAllFromListFailingTest() throws ReaperException { - JmxProxy coord = JmxProxyTest.mockJmxProxyImpl(); - when(coord.getTableNamesForKeyspace(Mockito.anyString())) - .thenReturn(Sets.newHashSet("table1", "table2", "table3", "table4")); - - RepairUnit unit = mock(RepairUnit.class); - when(unit.getBlacklistedTables()).thenReturn(Sets.newHashSet("table1", "table2", "table3")); - when(unit.getColumnFamilies()).thenReturn(Sets.newHashSet("table1", "table2", "table3")); - when(unit.getKeyspaceName()).thenReturn("test"); - - SegmentRunner.getTablesToRepair(coord, unit); - } - @Test public void getNodeMetricsInLocalDCAvailabilityForRemoteDCNodeTest() throws Exception { final AppContext context = new AppContext(); diff --git a/src/server/src/test/resources/cassandra-reaper-at-access-control-enabled.yaml b/src/server/src/test/resources/cassandra-reaper-at-access-control-enabled.yaml index 00775b609..740ad4288 100644 --- a/src/server/src/test/resources/cassandra-reaper-at-access-control-enabled.yaml +++ b/src/server/src/test/resources/cassandra-reaper-at-access-control-enabled.yaml @@ -1,5 +1,5 @@ # Copyright 2014-2017 Spotify AB -# Copyright 2016-2018 The Last Pickle Ltd +# Copyright 2016-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ repairRunThreadCount: 15 hangingRepairTimeoutMins: 1 storageType: memory incrementalRepair: false +blacklistTwcsTables: true enableDynamicSeedList: false jmxConnectionTimeoutInSeconds: 300 diff --git a/src/server/src/test/resources/cassandra-reaper-at.yaml b/src/server/src/test/resources/cassandra-reaper-at.yaml index f71da40b8..42fc19803 100644 --- a/src/server/src/test/resources/cassandra-reaper-at.yaml +++ b/src/server/src/test/resources/cassandra-reaper-at.yaml @@ -1,5 +1,5 @@ # Copyright 2014-2017 Spotify AB -# Copyright 2016-2018 The Last Pickle Ltd +# Copyright 2016-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ repairRunThreadCount: 15 hangingRepairTimeoutMins: 1 storageType: memory incrementalRepair: false +blacklistTwcsTables: true enableDynamicSeedList: false jmxConnectionTimeoutInSeconds: 300 datacenterAvailability: LOCAL diff --git a/src/server/src/test/resources/cassandra-reaper-cassandra-at.yaml b/src/server/src/test/resources/cassandra-reaper-cassandra-at.yaml index 5f83f2da9..5db5da0a7 100644 --- a/src/server/src/test/resources/cassandra-reaper-cassandra-at.yaml +++ b/src/server/src/test/resources/cassandra-reaper-cassandra-at.yaml @@ -1,5 +1,5 @@ # Copyright 2017-2017 Spotify AB -# Copyright 2017-2018 The Last Pickle Ltd +# Copyright 2017-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ repairRunThreadCount: 15 hangingRepairTimeoutMins: 1 storageType: cassandra incrementalRepair: false +blacklistTwcsTables: true jmxConnectionTimeoutInSeconds: 300 activateQueryLogger: true datacenterAvailability: LOCAL diff --git a/src/server/src/test/resources/cassandra-reaper-h2-at.yaml b/src/server/src/test/resources/cassandra-reaper-h2-at.yaml index 73626f1f2..fecea8969 100644 --- a/src/server/src/test/resources/cassandra-reaper-h2-at.yaml +++ b/src/server/src/test/resources/cassandra-reaper-h2-at.yaml @@ -1,5 +1,5 @@ # Copyright 2017-2017 Spotify AB -# Copyright 2017-2018 The Last Pickle Ltd +# Copyright 2017-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ repairRunThreadCount: 15 hangingRepairTimeoutMins: 1 storageType: database incrementalRepair: false +blacklistTwcsTables: true jmxConnectionTimeoutInSeconds: 300 datacenterAvailability: LOCAL diff --git a/src/server/src/test/resources/cassandra-reaper-postgres-at.yaml b/src/server/src/test/resources/cassandra-reaper-postgres-at.yaml index a21c1216b..e20f37722 100644 --- a/src/server/src/test/resources/cassandra-reaper-postgres-at.yaml +++ b/src/server/src/test/resources/cassandra-reaper-postgres-at.yaml @@ -1,5 +1,5 @@ # Copyright 2017-2017 Spotify AB -# Copyright 2017-2018 The Last Pickle Ltd +# Copyright 2017-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ repairRunThreadCount: 15 hangingRepairTimeoutMins: 1 storageType: postgres incrementalRepair: false +blacklistTwcsTables: true jmxConnectionTimeoutInSeconds: 300 datacenterAvailability: LOCAL diff --git a/src/server/src/test/resources/cassandra-reaper.yaml b/src/server/src/test/resources/cassandra-reaper.yaml index fbb00bb86..1d9fa4ffa 100644 --- a/src/server/src/test/resources/cassandra-reaper.yaml +++ b/src/server/src/test/resources/cassandra-reaper.yaml @@ -1,5 +1,5 @@ # Copyright 2014-2017 Spotify AB -# Copyright 2016-2018 The Last Pickle Ltd +# Copyright 2016-2019 The Last Pickle Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -30,6 +30,9 @@ repairRunThreadCount: 15 hangingRepairTimeoutMins: 30 incrementalRepair: false + +blacklistTwcsTables: true + # You can also use value 0 for scheduleDaysBetween, for continuous repairs. scheduleDaysBetween: 7 diff --git a/src/server/src/test/resources/io.cassandrareaper.acceptance/integration_reaper_functionality.feature b/src/server/src/test/resources/io.cassandrareaper.acceptance/integration_reaper_functionality.feature index 94d07f4db..a2781958c 100644 --- a/src/server/src/test/resources/io.cassandrareaper.acceptance/integration_reaper_functionality.feature +++ b/src/server/src/test/resources/io.cassandrareaper.acceptance/integration_reaper_functionality.feature @@ -18,6 +18,7 @@ Feature: Using Reaper Background: Given cluster seed host "127.0.0.1@test" points to cluster with name "test" And cluster "test" has keyspace "booya" with tables "booya1, booya2" + And cluster "test" has keyspace "booya" with tables "booya_twcs" And cluster "test" has keyspace "test_keyspace" with tables "test_table1, test_table2" And cluster "test" has keyspace "test_keyspace2" with tables "test_table1, test_table2" And cluster "test" has keyspace "test_keyspace3" with tables "test_table1, test_table2" @@ -132,6 +133,7 @@ Feature: Using Reaper And reaper has 0 repairs for the last added cluster When a new repair is added for the last added cluster and keyspace "booya" with the table "booya2" blacklisted And the last added repair has table "booya2" in the blacklist + And the last added repair has table "booya_twcs" in the blacklist When reaper is upgraded to latest And the last added repair has table "booya2" in the blacklist And deleting the last added cluster fails @@ -143,6 +145,26 @@ Feature: Using Reaper And the last added cluster is deleted Then reaper has no longer the last added cluster in storage ${cucumber.upgrade-versions} + + Scenario Outline: Create a cluster and a repair run with auto blacklist bypass and delete them + Given that reaper is running + And that we are going to use "127.0.0.1@test" as cluster seed host + And reaper has no cluster in storage + When an add-cluster request is made to reaper + Then reaper has the last added cluster in storage + And reaper has 0 repairs for the last added cluster + When a new repair is added for the last added cluster and keyspace "booya" for tables "booya_twcs,booya1" + And the last added repair has table "booya_twcs" in the table list + When reaper is upgraded to latest + And deleting the last added cluster fails + And the last added repair is activated + And we wait for at least 1 segments to be repaired + Then reaper has 1 started or done repairs for the last added cluster + When the last added repair is stopped + And the last added repair run is deleted + And the last added cluster is deleted + Then reaper has no longer the last added cluster in storage + ${cucumber.upgrade-versions} @all_nodes_reachable Scenario Outline: Create a cluster and an incremental repair run and delete them