summaryrefslogtreecommitdiffstats
path: root/clustercontroller-core
diff options
context:
space:
mode:
Diffstat (limited to 'clustercontroller-core')
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeChecker.java114
-rw-r--r--clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeCheckerTest.java116
2 files changed, 197 insertions, 33 deletions
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeChecker.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeChecker.java
index e242833fd0c..c323149e99b 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeChecker.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeChecker.java
@@ -13,10 +13,14 @@ import com.yahoo.vespa.clustercontroller.core.hostinfo.HostInfo;
import com.yahoo.vespa.clustercontroller.core.hostinfo.Metrics;
import com.yahoo.vespa.clustercontroller.core.hostinfo.StorageNode;
import com.yahoo.vespa.clustercontroller.utils.staterestapi.requests.SetUnitStateRequest;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
import static com.yahoo.vdslib.state.NodeType.STORAGE;
import static com.yahoo.vdslib.state.State.DOWN;
@@ -27,6 +31,7 @@ import static com.yahoo.vespa.clustercontroller.core.NodeStateChangeChecker.Resu
import static com.yahoo.vespa.clustercontroller.core.NodeStateChangeChecker.Result.createDisallowed;
import static com.yahoo.vespa.clustercontroller.utils.staterestapi.requests.SetUnitStateRequest.Condition.FORCE;
import static com.yahoo.vespa.clustercontroller.utils.staterestapi.requests.SetUnitStateRequest.Condition.SAFE;
+import static java.util.logging.Level.FINE;
/**
* Checks if a node can be upgraded.
@@ -35,8 +40,9 @@ import static com.yahoo.vespa.clustercontroller.utils.staterestapi.requests.SetU
*/
public class NodeStateChangeChecker {
- public static final String BUCKETS_METRIC_NAME = "vds.datastored.bucket_space.buckets_total";
- public static final Map<String, String> BUCKETS_METRIC_DIMENSIONS = Map.of("bucketSpace", "default");
+ private static final Logger log = Logger.getLogger(NodeStateChangeChecker.class.getName());
+ private static final String BUCKETS_METRIC_NAME = "vds.datastored.bucket_space.buckets_total";
+ private static final Map<String, String> BUCKETS_METRIC_DIMENSIONS = Map.of("bucketSpace", "default");
private final int requiredRedundancy;
private final HierarchicalGroupVisiting groupVisiting;
@@ -50,6 +56,8 @@ public class NodeStateChangeChecker {
this.clusterInfo = cluster.clusterInfo();
this.inMoratorium = inMoratorium;
this.maxNumberOfGroupsAllowedToBeDown = cluster.maxNumberOfGroupsAllowedToBeDown();
+ if ( ! groupVisiting.isHierarchical() && maxNumberOfGroupsAllowedToBeDown > 1)
+ throw new IllegalArgumentException("Cannot have both 1 group and maxNumberOfGroupsAllowedToBeDown > 1");
}
public static class Result {
@@ -214,26 +222,24 @@ public class NodeStateChangeChecker {
oldWantedState.getState() + ": " + oldWantedState.getDescription());
}
- Result otherGroupCheck = anotherNodeInAnotherGroupHasWantedState(nodeInfo);
- if (!otherGroupCheck.settingWantedStateIsAllowed()) {
- return otherGroupCheck;
- }
-
if (clusterState.getNodeState(nodeInfo.getNode()).getState() == DOWN) {
+ log.log(FINE, "node is DOWN, allow");
return allowSettingOfWantedState();
}
- if (anotherNodeInGroupAlreadyAllowed(nodeInfo, newDescription)) {
- return allowSettingOfWantedState();
- }
+ var result = otherNodesHaveWantedState(nodeInfo, newDescription);
+ if (result.isPresent())
+ return result.get();
Result allNodesAreUpCheck = checkAllNodesAreUp(clusterState);
if (!allNodesAreUpCheck.settingWantedStateIsAllowed()) {
+ log.log(FINE, "allNodesAreUpCheck: " + allNodesAreUpCheck);
return allNodesAreUpCheck;
}
Result checkDistributorsResult = checkDistributors(nodeInfo.getNode(), clusterState.getVersion());
if (!checkDistributorsResult.settingWantedStateIsAllowed()) {
+ log.log(FINE, "checkDistributors: "+ checkDistributorsResult);
return checkDistributorsResult;
}
@@ -241,31 +247,57 @@ public class NodeStateChangeChecker {
}
/**
- * Returns a disallow-result if there is another node (in another group, if hierarchical)
- * that has a wanted state != UP. We disallow more than 1 suspended node/group at a time.
+ * Returns an optional Result, where return value is:
+ * For flat setup: Return Optional.of(disallowed) if wanted state is set on some node, else Optional.empty
+ * For hierarchical setup: No wanted state for other nodes, return Optional.empty
+ * Wanted state for nodes/groups are not UP:
+ * if less than maxNumberOfGroupsAllowedToBeDown: return Optional.of(allowed)
+ * else: if node is in group with nodes already down: return Optional.of(allowed), else Optional.of(disallowed)
*/
- private Result anotherNodeInAnotherGroupHasWantedState(StorageNodeInfo nodeInfo) {
+ private Optional<Result> otherNodesHaveWantedState(StorageNodeInfo nodeInfo, String newDescription) {
+ Node node = nodeInfo.getNode();
+
if (groupVisiting.isHierarchical()) {
- SettableOptional<Result> anotherNodeHasWantedState = new SettableOptional<>();
-
- groupVisiting.visit(group -> {
- if (!groupContainsNode(group, nodeInfo.getNode())) {
- Result result = otherNodeInGroupHasWantedState(group);
- if (!result.settingWantedStateIsAllowed()) {
- anotherNodeHasWantedState.set(result);
- // Have found a node that is suspended, halt the visiting
- return false;
+ if (maxNumberOfGroupsAllowedToBeDown <= 1) {
+ SettableOptional<Result> anotherNodeHasWantedState = new SettableOptional<>();
+ groupVisiting.visit(group -> {
+ if (!groupContainsNode(group, node)) {
+ Result result = otherNodeInGroupHasWantedState(group);
+ if (!result.settingWantedStateIsAllowed()) {
+ anotherNodeHasWantedState.set(result);
+ // Have found a node that is suspended, halt the visiting
+ return false;
+ }
}
+ return true;
+ });
+ if (anotherNodeHasWantedState.isPresent()) {
+ log.log(FINE, "anotherNodeHasWantedState: " + anotherNodeHasWantedState.get());
+ return Optional.of(anotherNodeHasWantedState.get());
}
-
- return true;
- });
-
- return anotherNodeHasWantedState.asOptional().orElseGet(Result::allowSettingOfWantedState);
+ if (anotherNodeInGroupAlreadyAllowed(nodeInfo, newDescription)) {
+ log.log(FINE, "anotherNodeInGroupAlreadyAllowed, allow");
+ return Optional.of(allowSettingOfWantedState());
+ }
+ } else {
+ Set<Integer> groupsWithStorageNodesWantedStateNotUp = groupsWithStorageNodesWantedStateNotUp();
+ String disallowMessage = "At most nodes in " + maxNumberOfGroupsAllowedToBeDown + " groups can have wanted state";
+ if (groupsWithStorageNodesWantedStateNotUp.size() < maxNumberOfGroupsAllowedToBeDown)
+ return Optional.of(allowSettingOfWantedState());
+ if (groupsWithStorageNodesWantedStateNotUp.size() > maxNumberOfGroupsAllowedToBeDown)
+ return Optional.of(createDisallowed(disallowMessage));
+ if (aGroupContainsNode(groupsWithStorageNodesWantedStateNotUp, node))
+ return Optional.of(allowSettingOfWantedState());
+
+ return Optional.of(createDisallowed(disallowMessage));
+ }
} else {
// Return a disallow-result if there is another node with a wanted state
- return otherNodeHasWantedState(nodeInfo);
+ var otherNodeHasWantedState = otherNodeHasWantedState(nodeInfo);
+ if ( ! otherNodeHasWantedState.settingWantedStateIsAllowed())
+ return Optional.of(otherNodeHasWantedState);
}
+ return Optional.empty();
}
/** Returns a disallow-result, if there is a node in the group with wanted state != UP. */
@@ -354,6 +386,22 @@ public class NodeStateChangeChecker {
return false;
}
+ private boolean aGroupContainsNode(Collection<Integer> groupIndexes, Node node) {
+ for (Group group : getGroupsWithIndexes(groupIndexes)) {
+ if (groupContainsNode(group, node))
+ return true;
+ }
+
+ return false;
+ }
+
+ private List<Group> getGroupsWithIndexes(Collection<Integer> groupIndexes) {
+ return clusterInfo.getStorageNodeInfos().stream()
+ .map(NodeInfo::getGroup)
+ .filter(group -> groupIndexes.contains(group.getIndex()))
+ .collect(Collectors.toList());
+ }
+
private Result checkAllNodesAreUp(ClusterState clusterState) {
// This method verifies both storage nodes and distributors are up (or retired).
// The complicated part is making a summary error message.
@@ -441,4 +489,14 @@ public class NodeStateChangeChecker {
return allowSettingOfWantedState();
}
+ private Set<Integer> groupsWithStorageNodesWantedStateNotUp() {
+ return clusterInfo.getStorageNodeInfos().stream()
+ .filter(sni -> !UP.equals(sni.getWantedState().getState()))
+ .map(NodeInfo::getGroup)
+ .filter(Objects::nonNull)
+ .filter(Group::isLeafGroup)
+ .map(Group::getIndex)
+ .collect(Collectors.toSet());
+ }
+
}
diff --git a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeCheckerTest.java b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeCheckerTest.java
index 45ca07c88e4..08265248959 100644
--- a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeCheckerTest.java
+++ b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeCheckerTest.java
@@ -114,7 +114,7 @@ public class NodeStateChangeCheckerTest {
}
@Test
- void testCanUpgradeForce() {
+ void testCanUpgradeWithForce() {
var nodeStateChangeChecker = createChangeChecker(createCluster(1));
NodeState newState = new NodeState(STORAGE, INITIALIZING);
Result result = nodeStateChangeChecker.evaluateTransition(
@@ -152,7 +152,7 @@ public class NodeStateChangeCheckerTest {
void testSafeMaintenanceDisallowedWhenOtherStorageNodeInFlatClusterIsSuspended() {
// Nodes 0-3, storage node 0 being in maintenance with "Orchestrator" description.
ContentCluster cluster = createCluster(4);
- cluster.clusterInfo().getStorageNodeInfo(0).setWantedState(new NodeState(STORAGE, MAINTENANCE).setDescription("Orchestrator"));
+ setStorageNodeWantedStateToMaintenance(cluster, 0);
var nodeStateChangeChecker = createChangeChecker(cluster);
ClusterState clusterStateWith0InMaintenance = clusterState(String.format(
"version:%d distributor:4 storage:4 .0.s:m",
@@ -168,6 +168,101 @@ public class NodeStateChangeCheckerTest {
}
@Test
+ void testMaintenanceAllowedFor2Of4Groups() {
+ // 4 groups with 1 node in each group
+ Collection<ConfiguredNode> nodes = createNodes(4);
+ StorDistributionConfig config = createDistributionConfig(4, 4);
+
+ int maxNumberOfGroupsAllowedToBeDown = 2;
+ var cluster = new ContentCluster("Clustername", nodes, new Distribution(config), maxNumberOfGroupsAllowedToBeDown);
+ setAllNodesUp(cluster, HostInfo.createHostInfo(createDistributorHostInfo(4, 5, 6)));
+ var nodeStateChangeChecker = createChangeChecker(cluster);
+
+ // All nodes up, set a storage node in group 0 to maintenance
+ {
+ int nodeIndex = 0;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, defaultAllUpClusterState());
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ // Node in group 0 in maintenance, set storage node in group 1 to maintenance
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:4 .0.s:d storage:4 .0.s:m", currentClusterStateVersion));
+ int nodeIndex = 1;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, clusterState);
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ // Nodes in group 0 and 1 in maintenance, try to set storage node in group 2 to maintenance, should fail
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:4 storage:4 .0.s:m .1.s:m", currentClusterStateVersion));
+ int nodeIndex = 2;
+ Node node = new Node(STORAGE, nodeIndex);
+ Result result = nodeStateChangeChecker.evaluateTransition(node, clusterState, SAFE, UP_NODE_STATE, MAINTENANCE_NODE_STATE);
+ assertFalse(result.settingWantedStateIsAllowed(), result.toString());
+ assertFalse(result.wantedStateAlreadySet());
+ assertEquals("At most nodes in 2 groups can have wanted state", result.getReason());
+ }
+
+ }
+
+ @Test
+ void testMaintenanceAllowedFor2Of4Groups8Nodes() {
+ // 4 groups with 2 node in each group
+ Collection<ConfiguredNode> nodes = createNodes(8);
+ StorDistributionConfig config = createDistributionConfig(8, 4);
+
+ int maxNumberOfGroupsAllowedToBeDown = 2;
+ var cluster = new ContentCluster("Clustername", nodes, new Distribution(config), maxNumberOfGroupsAllowedToBeDown);
+ setAllNodesUp(cluster, HostInfo.createHostInfo(createDistributorHostInfo(4, 5, 6)));
+ var nodeStateChangeChecker = createChangeChecker(cluster);
+
+ // All nodes up, set a storage node in group 0 to maintenance
+ {
+ ClusterState clusterState = defaultAllUpClusterState(8);
+ int nodeIndex = 0;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, clusterState);
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ // 1 Node in group 0 in maintenance, try to set node 1 in group 0 to maintenance
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:8 .0.s:d storage:8 .0.s:m", currentClusterStateVersion));
+ int nodeIndex = 1;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, clusterState);
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ // Nodes in group 0 in maintenance, try to set storage node 2 in group 1 to maintenance
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:8 storage:8 .0.s:m .1.s:m", currentClusterStateVersion));
+ int nodeIndex = 2;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, clusterState);
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ // 2 nodes in group 0 and 1 in group 1 in maintenance, try to set storage node 4 in group 2 to maintenance, should fail (different group)
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:8 storage:8 .0.s:m .1.s:m .2.s:m", currentClusterStateVersion));
+ int nodeIndex = 4;
+ Node node = new Node(STORAGE, nodeIndex);
+ Result result = nodeStateChangeChecker.evaluateTransition(node, clusterState, SAFE, UP_NODE_STATE, MAINTENANCE_NODE_STATE);
+ assertFalse(result.settingWantedStateIsAllowed(), result.toString());
+ assertFalse(result.wantedStateAlreadySet());
+ assertEquals("At most nodes in 2 groups can have wanted state", result.getReason());
+ }
+
+ // 2 nodes in group 0 and 1 in group 1 in maintenance, try to set storage node 3 in group 1 to maintenance
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:8 storage:8 .0.s:m .1.s:m .2.s:m", currentClusterStateVersion));
+ int nodeIndex = 3;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, clusterState);
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ }
+
+ @Test
void testSafeMaintenanceDisallowedWhenOtherDistributorInFlatClusterIsSuspended() {
// Nodes 0-3, distributor 0 being down with "Orchestrator" description.
ContentCluster cluster = createCluster(4);
@@ -439,10 +534,9 @@ public class NodeStateChangeCheckerTest {
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
for (int x = 0; x < cluster.clusterInfo().getConfiguredNodes().size(); x++) {
- State state = UP;
- cluster.clusterInfo().getDistributorNodeInfo(x).setReportedState(new NodeState(DISTRIBUTOR, state), 0);
+ cluster.clusterInfo().getDistributorNodeInfo(x).setReportedState(new NodeState(DISTRIBUTOR, UP), 0);
cluster.clusterInfo().getDistributorNodeInfo(x).setHostInfo(HostInfo.createHostInfo(createDistributorHostInfo(4, 5, 6)));
- cluster.clusterInfo().getStorageNodeInfo(x).setReportedState(new NodeState(STORAGE, state), 0);
+ cluster.clusterInfo().getStorageNodeInfo(x).setReportedState(new NodeState(STORAGE, UP), 0);
}
return nodeStateChangeChecker.evaluateTransition(
@@ -763,6 +857,18 @@ public class NodeStateChangeCheckerTest {
return configBuilder.build();
}
+ private void checkSettingToMaintenanceIsAllowed(int nodeIndex, NodeStateChangeChecker nodeStateChangeChecker, ClusterState clusterState) {
+ Node node = new Node(STORAGE, nodeIndex);
+ Result result = nodeStateChangeChecker.evaluateTransition(node, clusterState, SAFE, UP_NODE_STATE, MAINTENANCE_NODE_STATE);
+ assertTrue(result.settingWantedStateIsAllowed());
+ assertFalse(result.wantedStateAlreadySet());
+ assertEquals("Preconditions fulfilled and new state different", result.getReason());
+ }
+
+ private void setStorageNodeWantedStateToMaintenance(ContentCluster cluster, int nodeIndex) {
+ cluster.clusterInfo().getStorageNodeInfo(nodeIndex).setWantedState(MAINTENANCE_NODE_STATE.setDescription("Orchestrator"));
+ }
+
private void setStorageNodeWantedState(ContentCluster cluster, int nodeIndex, State state, String description) {
NodeState nodeState = new NodeState(STORAGE, state);
cluster.clusterInfo().getStorageNodeInfo(nodeIndex).setWantedState(nodeState.setDescription(description));