blob: 3ad03fb6a0e1e87672b96458a610a0ee8159ccbd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.cassandra.distributed.test.log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import org.apache.cassandra.harry.sut.TokenPlacementModel;
import org.apache.cassandra.harry.sut.TokenPlacementModel.DCReplicas;
public class ModelState
{
public final int maxClusterSize;
public final int maxConcurrency;
public final int uniqueNodes;
public final int rejected;
public final int[] cancelled;
public final int[] finished;
public final int bootstrappingCount;
public final List<TokenPlacementModel.Node> currentNodes;
public final Map<String, List<TokenPlacementModel.Node>> nodesByDc;
public final List<TokenPlacementModel.Node> registeredNodes;
public final List<TokenPlacementModel.Node> leavingNodes;
public final List<TokenPlacementModel.Node> movingNodes;
public final List<SimulatedOperation> inFlightOperations;
public final PlacementSimulator.SimulatedPlacements simulatedPlacements;
public final TokenPlacementModel.NodeFactory nodeFactory;
public static ModelState empty(TokenPlacementModel.NodeFactory nodeFactory, int maxClusterSize, int maxConcurrency)
{
return new ModelState(maxClusterSize, maxConcurrency,
0, 0,
new int[]{ 0, 0, 0, 0 },
new int[]{ 0, 0, 0, 0 },
Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(),
null,
nodeFactory);
}
public static Map<String, List<TokenPlacementModel.Node>> groupByDc(List<TokenPlacementModel.Node> nodes)
{
// using treemap here since it is much easier to read/debug when it comes to that
Map<String, List<TokenPlacementModel.Node>> grouped = new TreeMap<>();
for (TokenPlacementModel.Node node : nodes)
{
grouped.computeIfAbsent(node.dc(), (k) -> new ArrayList<>())
.add(node);
}
return grouped;
}
private ModelState(int maxClusterSize,
int maxConcurrency,
int uniqueNodes,
int rejected,
int[] cancelled,
int[] finished,
List<TokenPlacementModel.Node> currentNodes,
List<TokenPlacementModel.Node> registeredNodes,
List<TokenPlacementModel.Node> leavingNodes,
List<TokenPlacementModel.Node> movingNodes,
List<SimulatedOperation> operationStates,
PlacementSimulator.SimulatedPlacements simulatedPlacements,
TokenPlacementModel.NodeFactory nodeFactory)
{
this.maxClusterSize = maxClusterSize;
this.maxConcurrency = maxConcurrency;
this.uniqueNodes = uniqueNodes;
this.rejected = rejected;
this.cancelled = cancelled;
this.finished = finished;
this.currentNodes = currentNodes;
this.registeredNodes = registeredNodes;
this.nodesByDc = groupByDc(currentNodes);
this.leavingNodes = leavingNodes;
this.movingNodes = movingNodes;
this.inFlightOperations = operationStates;
this.simulatedPlacements = simulatedPlacements;
bootstrappingCount = (int) operationStates.stream()
.filter(s -> s.type == SimulatedOperation.Type.JOIN)
.count();
this.nodeFactory = nodeFactory;
}
public Transformer transformer()
{
return new Transformer(this);
}
private boolean withinConcurrencyLimit()
{
return inFlightOperations.size() < maxConcurrency;
}
public boolean shouldBootstrap()
{
return withinConcurrencyLimit() && bootstrappingCount + currentNodes.size() < maxClusterSize;
}
public boolean shouldLeave(TokenPlacementModel.ReplicationFactor rf, Random rng)
{
return canRemove(rf) && rng.nextDouble() > 0.7;
}
public boolean shouldMove(TokenPlacementModel.ReplicationFactor rf, Random rng)
{
return canRemove(rf) && rng.nextDouble() > 0.7;
}
public boolean shouldReplace(TokenPlacementModel.ReplicationFactor rf, Random rng)
{
return canRemove(rf) && rng.nextDouble() > 0.8;
}
private boolean canRemove(TokenPlacementModel.ReplicationFactor rfs)
{
if (!withinConcurrencyLimit()) return false;
for (Map.Entry<String, DCReplicas> e : rfs.asMap().entrySet())
{
String dc = e.getKey();
int rf = e.getValue().totalCount;
List<TokenPlacementModel.Node> nodes = nodesByDc.get(dc);
Set<TokenPlacementModel.Node> nodesInDc = nodes == null ? new HashSet<>() : new HashSet<>(nodes);
for (SimulatedOperation op : inFlightOperations)
nodesInDc.removeAll(Arrays.asList(op.nodes));
if (nodesInDc.size() > rf)
return true;
}
return false;
}
public boolean shouldCancel(Random rng)
{
return rng.nextDouble() > 0.95;
}
public String toString()
{
return "ModelState{" +
"uniqueNodes=" + uniqueNodes +
", rejectedOps=" + rejected +
", cancelledOps=" + Arrays.toString(cancelled) +
", finishedOps=" + Arrays.toString(finished) +
", bootstrappedNodes=" + currentNodes +
", leavingNodes=" + leavingNodes +
", operationStates=" + inFlightOperations +
", maxClusterSize=" + maxClusterSize +
", maxConcurrency=" + maxConcurrency +
'}';
}
public static class Transformer
{
private final int maxClusterSize;
private final int maxConcurrency;
private int uniqueNodes;
private int rejected;
// join/replace/leave/move
private int[] cancelled;
private int[] finished;
private List<TokenPlacementModel.Node> currentNodes;
private List<TokenPlacementModel.Node> registeredNodes;
private List<TokenPlacementModel.Node> leavingNodes;
private List<TokenPlacementModel.Node> movingNodes;
private List<SimulatedOperation> operationStates;
private PlacementSimulator.SimulatedPlacements simulatedPlacements;
private TokenPlacementModel.NodeFactory nodeFactory;
private Transformer(ModelState source)
{
this.maxClusterSize = source.maxClusterSize;
this.maxConcurrency = source.maxConcurrency;
this.uniqueNodes = source.uniqueNodes;
this.rejected = source.rejected;
this.cancelled = source.cancelled;
this.finished = source.finished;
this.currentNodes = source.currentNodes;
this.registeredNodes = source.registeredNodes;
this.leavingNodes = source.leavingNodes;
this.movingNodes = source.movingNodes;
this.operationStates = source.inFlightOperations;
this.simulatedPlacements = source.simulatedPlacements;
this.nodeFactory = source.nodeFactory;
}
public Transformer incrementUniqueNodes()
{
uniqueNodes++;
return this;
}
public Transformer incrementRejected()
{
rejected++;
return this;
}
public Transformer incrementCancelledJoin()
{
cancelled[0]++;
return this;
}
public Transformer incrementCancelledReplace()
{
cancelled[1]++;
return this;
}
public Transformer incrementCancelledLeave()
{
cancelled[2]++;
return this;
}
public Transformer incrementCancelledMove()
{
cancelled[3]++;
return this;
}
public Transformer addOperation(SimulatedOperation operation)
{
operationStates = new ArrayList<>(operationStates);
operationStates.add(operation);
return this;
}
public Transformer removeOperation(SimulatedOperation operation)
{
operationStates = new ArrayList<>(operationStates);
operationStates.remove(operation);
return this;
}
public Transformer withJoined(TokenPlacementModel.Node node)
{
addToCluster(node);
finished[0]++;
return this;
}
public Transformer recycleRejected(TokenPlacementModel.Node node)
{
registeredNodes = new ArrayList<>(registeredNodes);
registeredNodes.add(node);
return this;
}
public Transformer withMoved(TokenPlacementModel.Node movingNode, TokenPlacementModel.Node movedTo)
{
assert currentNodes.contains(movingNode) : movingNode;
List<TokenPlacementModel.Node> tmp = currentNodes;
currentNodes = new ArrayList<>();
for (TokenPlacementModel.Node n : tmp)
{
if (n.idx() == movingNode.idx())
currentNodes.add(movedTo);
else
currentNodes.add(n);
}
finished[3]++;
assert movingNodes.contains(movingNode);
movingNodes = new ArrayList<>(movingNodes);
movingNodes.remove(movingNode);
return this;
}
private void addToCluster(TokenPlacementModel.Node node)
{
// called during both join and replacement
currentNodes = new ArrayList<>(currentNodes);
currentNodes.add(node);
}
public Transformer markMoving(TokenPlacementModel.Node moving)
{
assert currentNodes.contains(moving);
movingNodes = new ArrayList<>(movingNodes);
movingNodes.add(moving);
return this;
}
public Transformer markLeaving(TokenPlacementModel.Node leaving)
{
assert currentNodes.contains(leaving);
leavingNodes = new ArrayList<>(leavingNodes);
leavingNodes.add(leaving);
return this;
}
public Transformer withLeft(TokenPlacementModel.Node node)
{
assert currentNodes.contains(node);
// for now... assassinate may change this assertion
assert leavingNodes.contains(node);
finished[2]++;
removeFromCluster(node);
return this;
}
private void removeFromCluster(TokenPlacementModel.Node node)
{
// called during both decommission and replacement
currentNodes = new ArrayList<>(currentNodes);
currentNodes.remove(node);
leavingNodes = new ArrayList<>(leavingNodes);
leavingNodes.remove(node);
}
public Transformer withReplaced(TokenPlacementModel.Node oldNode, TokenPlacementModel.Node newNode)
{
addToCluster(newNode);
removeFromCluster(oldNode);
finished[1]++;
return this;
}
public Transformer updateSimulation(PlacementSimulator.SimulatedPlacements simulatedPlacements)
{
this.simulatedPlacements = simulatedPlacements;
return this;
}
public ModelState transform()
{
return new ModelState(maxClusterSize,
maxConcurrency,
uniqueNodes,
rejected,
cancelled,
finished,
currentNodes,
registeredNodes,
leavingNodes,
movingNodes,
operationStates,
simulatedPlacements,
nodeFactory);
}
}
}