summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/FleetControllerOptions.java7
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java1
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java6
-rw-r--r--document/src/main/java/com/yahoo/document/DocumentPut.java2
-rw-r--r--document/src/main/java/com/yahoo/document/DocumentRemove.java7
-rw-r--r--document/src/main/java/com/yahoo/document/DocumentUpdate.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java10
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java89
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java24
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceDeployment.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java16
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java33
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java8
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java19
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node55.json6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes-recursive-include-deprovisioned.json2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes-recursive.json4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes.json4
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/states-recursive.json2
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java99
-rw-r--r--vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java78
32 files changed, 295 insertions, 170 deletions
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/FleetControllerOptions.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/FleetControllerOptions.java
index a088b50f078..0ad0a59a72a 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/FleetControllerOptions.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/FleetControllerOptions.java
@@ -16,6 +16,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
+import java.util.stream.Collectors;
/**
* This class represents all the options that can be set in the fleetcontroller.
@@ -249,6 +250,12 @@ public class FleetControllerOptions implements Cloneable {
sb.append("<tr><td><nobr>Max deferred task version wait time</nobr></td><td align=\"right\">").append(maxDeferredTaskVersionWaitTime.toMillis()).append("ms</td></tr>");
sb.append("<tr><td><nobr>Cluster has global document types configured</nobr></td><td align=\"right\">").append(clusterHasGlobalDocumentTypes).append("</td></tr>");
sb.append("<tr><td><nobr>Enable 2-phase cluster state activation protocol</nobr></td><td align=\"right\">").append(enableTwoPhaseClusterStateActivation).append("</td></tr>");
+ sb.append("<tr><td><nobr>Cluster auto feed block on resource exhaustion enabled</nobr></td><td align=\"right\">")
+ .append(clusterFeedBlockEnabled).append("</td></tr>");
+ sb.append("<tr><td><nobr>Feed block limits</nobr></td><td align=\"right\">")
+ .append(clusterFeedBlockLimit.entrySet().stream()
+ .map(kv -> String.format("%s: %.2f%%", kv.getKey(), kv.getValue() * 100.0))
+ .collect(Collectors.joining("<br/>"))).append("</td></tr>");
sb.append("</table>");
}
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
index bc131b27080..a5dc3f21a1d 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
@@ -76,7 +76,6 @@ public interface ModelContext {
@ModelFeatureFlag(owners = {"baldersheim"}) default boolean skipMbusReplyThread() { throw new UnsupportedOperationException("TODO specify default value"); }
@ModelFeatureFlag(owners = {"tokle"}) default boolean useAccessControlTlsHandshakeClientAuth() { return false; }
@ModelFeatureFlag(owners = {"baldersheim"}) default boolean useAsyncMessageHandlingOnSchedule() { throw new UnsupportedOperationException("TODO specify default value"); }
- @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter = "7.348") default int contentNodeBucketDBStripeBits() { return 4; }
@ModelFeatureFlag(owners = {"baldersheim"}, removeAfter = "7.350") default int mergeChunkSize() { return 0x2000000; }
@ModelFeatureFlag(owners = {"baldersheim"}) default double feedConcurrency() { throw new UnsupportedOperationException("TODO specify default value"); }
@ModelFeatureFlag(owners = {"baldersheim"}) default boolean useBucketExecutorForLidSpaceCompact() { throw new UnsupportedOperationException("TODO specify default value"); }
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java
index 14658e57c1b..f4b79129ada 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java
@@ -77,10 +77,8 @@ public final class ClusterSpec {
*/
public boolean isExclusive() { return exclusive; }
- /** Whether this cluster has state */
- public boolean isStateful() {
- return stateful;
- }
+ /** Returns whether this cluster has state */
+ public boolean isStateful() { return stateful; }
public ClusterSpec with(Optional<Group> newGroup) {
return new ClusterSpec(type, id, newGroup, vespaVersion, exclusive, combinedId, dockerImageRepo, stateful);
diff --git a/document/src/main/java/com/yahoo/document/DocumentPut.java b/document/src/main/java/com/yahoo/document/DocumentPut.java
index e24388cd65f..25246dc9a9e 100644
--- a/document/src/main/java/com/yahoo/document/DocumentPut.java
+++ b/document/src/main/java/com/yahoo/document/DocumentPut.java
@@ -54,7 +54,7 @@ public class DocumentPut extends DocumentOperation {
if (o == null || getClass() != o.getClass()) return false;
DocumentPut that = (DocumentPut) o;
return document.equals(that.document) &&
- getCondition().equals(that.getCondition());
+ Objects.equals(getCondition(), that.getCondition());
}
@Override
diff --git a/document/src/main/java/com/yahoo/document/DocumentRemove.java b/document/src/main/java/com/yahoo/document/DocumentRemove.java
index 79f80713c44..a815d9c0a5a 100644
--- a/document/src/main/java/com/yahoo/document/DocumentRemove.java
+++ b/document/src/main/java/com/yahoo/document/DocumentRemove.java
@@ -1,6 +1,8 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.document;
+import java.util.Objects;
+
/**
* @author baldersheim
*/
@@ -21,9 +23,10 @@ public class DocumentRemove extends DocumentOperation {
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof DocumentRemove)) return false;
+ if ( ! (o instanceof DocumentRemove)) return false;
DocumentRemove that = (DocumentRemove) o;
- if (!docId.equals(that.docId)) return false;
+ if ( ! docId.equals(that.docId)) return false;
+ if ( ! Objects.equals(getCondition(), that.getCondition())) return false;
return true;
}
diff --git a/document/src/main/java/com/yahoo/document/DocumentUpdate.java b/document/src/main/java/com/yahoo/document/DocumentUpdate.java
index 5c748f48f15..cba51ee999e 100644
--- a/document/src/main/java/com/yahoo/document/DocumentUpdate.java
+++ b/document/src/main/java/com/yahoo/document/DocumentUpdate.java
@@ -19,6 +19,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
/**
@@ -362,9 +363,10 @@ public class DocumentUpdate extends DocumentOperation implements Iterable<FieldP
if (docId != null ? !docId.equals(that.docId) : that.docId != null) return false;
if (documentType != null ? !documentType.equals(that.documentType) : that.documentType != null) return false;
- if (!fieldPathUpdates.equals(that.fieldPathUpdates)) return false;
- if (!id2FieldUpdates.equals(that.id2FieldUpdates)) return false;
- if (this.getCreateIfNonExistent() != ((DocumentUpdate) o).getCreateIfNonExistent()) return false;
+ if ( ! fieldPathUpdates.equals(that.fieldPathUpdates)) return false;
+ if ( ! id2FieldUpdates.equals(that.id2FieldUpdates)) return false;
+ if (this.getCreateIfNonExistent() != that.getCreateIfNonExistent()) return false;
+ if ( ! Objects.equals(getCondition(), that.getCondition())) return false;
return true;
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java
index b0b61e8a6b2..19c1fa090c9 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeList.java
@@ -69,6 +69,16 @@ public class NodeList extends AbstractFilteringList<Node, NodeList> {
return matching(node -> node.allocation().isPresent() && node.allocation().get().membership().cluster().type().isContainer());
}
+ /** Returns the subset of nodes that run a stateless service */
+ public NodeList stateless() {
+ return matching(node -> node.allocation().isPresent() && ! node.allocation().get().membership().cluster().isStateful());
+ }
+
+ /** Returns the subset of nodes that run a stateful service */
+ public NodeList stateful() {
+ return matching(node -> node.allocation().isPresent() && node.allocation().get().membership().cluster().isStateful());
+ }
+
/** Returns the subset of nodes that are currently changing their Vespa version */
public NodeList changingVersion() {
return matching(node -> node.status().vespaVersion().isPresent() &&
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
index 4eb38fa650e..8e14b61db9a 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
@@ -29,6 +29,7 @@ import com.yahoo.vespa.hosted.provision.maintenance.NodeFailer;
import com.yahoo.vespa.hosted.provision.maintenance.PeriodicApplicationMaintainer;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.Allocation;
+import com.yahoo.vespa.hosted.provision.node.History;
import com.yahoo.vespa.hosted.provision.node.IP;
import com.yahoo.vespa.hosted.provision.node.NodeAcl;
import com.yahoo.vespa.hosted.provision.node.filter.NodeFilter;
@@ -410,7 +411,7 @@ public class NodeRepository extends AbstractComponent {
for (Node node : nodes) {
if ( ! node.flavor().getType().equals(Flavor.Type.DOCKER_CONTAINER))
illegal("Cannot add " + node + ": This is not a docker node");
- if ( ! node.allocation().isPresent())
+ if (node.allocation().isEmpty())
illegal("Cannot add " + node + ": Docker containers needs to be allocated");
Optional<Node> existing = getNode(node.hostname());
if (existing.isPresent())
@@ -514,7 +515,13 @@ public class NodeRepository extends AbstractComponent {
* transaction commits.
*/
public List<Node> deactivate(List<Node> nodes, ApplicationTransaction transaction) {
- return db.writeTo(State.inactive, nodes, Agent.application, Optional.empty(), transaction.nested());
+ var stateless = NodeList.copyOf(nodes).stateless();
+ var stateful = NodeList.copyOf(nodes).stateful();
+ List<Node> written = new ArrayList<>();
+ written.addAll(deallocate(stateless.asList(), Agent.application, "Deactivated by application", transaction.nested()));
+ written.addAll(db.writeTo(State.inactive, stateful.asList(), Agent.application, Optional.empty(), transaction.nested()));
+ return written;
+
}
/** Removes this application: Active nodes are deactivated while all non-active nodes are set dirty. */
@@ -531,22 +538,11 @@ public class NodeRepository extends AbstractComponent {
}
/** Move nodes to the dirty state */
- public List<Node> setDirty(List<Node> nodes, Agent agent, String reason) {
- return performOn(NodeListFilter.from(nodes), (node, lock) -> setDirty(node, agent, reason));
- }
-
- /**
- * Set a node dirty, allowed if it is in the provisioned, inactive, failed or parked state.
- * Use this to clean newly provisioned nodes or to recycle failed nodes which have been repaired or put on hold.
- *
- * @throws IllegalArgumentException if the node has hardware failure
- */
- public Node setDirty(Node node, Agent agent, String reason) {
- return db.writeTo(State.dirty, node, agent, Optional.of(reason));
+ public List<Node> deallocate(List<Node> nodes, Agent agent, String reason) {
+ return performOn(NodeListFilter.from(nodes), (node, lock) -> deallocate(node, agent, reason));
}
-
- public List<Node> dirtyRecursively(String hostname, Agent agent, String reason) {
+ public List<Node> deallocateRecursively(String hostname, Agent agent, String reason) {
Node nodeToDirty = getNode(hostname).orElseThrow(() ->
new IllegalArgumentException("Could not deallocate " + hostname + ": Node not found"));
@@ -568,7 +564,37 @@ public class NodeRepository extends AbstractComponent {
illegal("Could not deallocate " + nodeToDirty + ": " +
hostnamesNotAllowedToDirty + " are not in states [provisioned, failed, parked, breakfixed]");
- return nodesToDirty.stream().map(node -> setDirty(node, agent, reason)).collect(Collectors.toList());
+ return nodesToDirty.stream().map(node -> deallocate(node, agent, reason)).collect(Collectors.toList());
+ }
+
+ /**
+ * Set a node dirty or parked, allowed if it is in the provisioned, inactive, failed or parked state.
+ * Use this to clean newly provisioned nodes or to recycle failed nodes which have been repaired or put on hold.
+ */
+ public Node deallocate(Node node, Agent agent, String reason) {
+ NestedTransaction transaction = new NestedTransaction();
+ Node deallocated = deallocate(node, agent, reason, transaction);
+ transaction.commit();
+ return deallocated;
+ }
+
+ public List<Node> deallocate(List<Node> nodes, Agent agent, String reason, NestedTransaction transaction) {
+ return nodes.stream().map(node -> deallocate(node, agent, reason, transaction)).collect(Collectors.toList());
+ }
+
+ public Node deallocate(Node node, Agent agent, String reason, NestedTransaction transaction) {
+ if (node.state() != State.parked && agent != Agent.operator
+ && (node.status().wantToDeprovision() || retiredByOperator(node)))
+ return park(node.hostname(), false, agent, reason, transaction);
+ else
+ return db.writeTo(State.dirty, List.of(node), agent, Optional.of(reason), transaction).get(0);
+ }
+
+ private static boolean retiredByOperator(Node node) {
+ return node.status().wantToRetire() && node.history().event(History.Event.Type.wantToRetire)
+ .map(History.Event::agent)
+ .map(agent -> agent == Agent.operator)
+ .orElse(false);
}
/**
@@ -597,7 +623,14 @@ public class NodeRepository extends AbstractComponent {
* @throws NoSuchNodeException if the node is not found
*/
public Node park(String hostname, boolean keepAllocation, Agent agent, String reason) {
- return move(hostname, keepAllocation, State.parked, agent, Optional.of(reason));
+ NestedTransaction transaction = new NestedTransaction();
+ Node parked = park(hostname, keepAllocation, agent, reason, transaction);
+ transaction.commit();
+ return parked;
+ }
+
+ public Node park(String hostname, boolean keepAllocation, Agent agent, String reason, NestedTransaction transaction) {
+ return move(hostname, keepAllocation, State.parked, agent, Optional.of(reason), transaction);
}
/**
@@ -644,6 +677,14 @@ public class NodeRepository extends AbstractComponent {
}
private Node move(String hostname, boolean keepAllocation, State toState, Agent agent, Optional<String> reason) {
+ NestedTransaction transaction = new NestedTransaction();
+ Node moved = move(hostname, keepAllocation, toState, agent, reason, transaction);
+ transaction.commit();
+ return moved;
+ }
+
+ private Node move(String hostname, boolean keepAllocation, State toState, Agent agent, Optional<String> reason,
+ NestedTransaction transaction) {
Node node = getNode(hostname).orElseThrow(() ->
new NoSuchNodeException("Could not move " + hostname + " to " + toState + ": Node not found"));
@@ -651,10 +692,17 @@ public class NodeRepository extends AbstractComponent {
node = node.withoutAllocation();
}
- return move(node, toState, agent, reason);
+ return move(node, toState, agent, reason, transaction);
}
private Node move(Node node, State toState, Agent agent, Optional<String> reason) {
+ NestedTransaction transaction = new NestedTransaction();
+ Node moved = move(node, toState, agent, reason, transaction);
+ transaction.commit();
+ return moved;
+ }
+
+ private Node move(Node node, State toState, Agent agent, Optional<String> reason, NestedTransaction transaction) {
if (toState == Node.State.active && node.allocation().isEmpty())
illegal("Could not set " + node + " active. It has no allocation.");
@@ -667,7 +715,7 @@ public class NodeRepository extends AbstractComponent {
illegal("Could not set " + node + " active: Same cluster and index as " + currentActive);
}
}
- return db.writeTo(toState, node, agent, reason);
+ return db.writeTo(toState, List.of(node), agent, reason, transaction).get(0);
}
}
@@ -981,4 +1029,5 @@ public class NodeRepository extends AbstractComponent {
private void illegal(String message) {
throw new IllegalArgumentException(message);
}
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
index b55f49722cb..1a47af6b929 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirer.java
@@ -112,7 +112,7 @@ public class FailedExpirer extends NodeRepositoryMaintainer {
nodesToRecycle.add(candidate);
}
}
- nodeRepository.setDirty(nodesToRecycle, Agent.FailedExpirer, "Expired by FailedExpirer");
+ nodeRepository.deallocate(nodesToRecycle, Agent.FailedExpirer, "Expired by FailedExpirer");
}
/** Returns whether the current node fail count should be used as an indicator of hardware issue */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java
index 392146f6ead..231d2ac08b1 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveExpirer.java
@@ -14,14 +14,9 @@ import java.util.List;
/**
* Maintenance job which moves inactive nodes to dirty or parked after timeout.
*
- * The timeout is in place for two reasons:
- *
- * - To ensure that the new application configuration has time to
- * propagate before the node is used for something else.
- *
- * - To provide a grace period in which nodes can be brought back to active
- * if they were deactivated in error. As inactive nodes retain their state
- * they can be brought back to active and correct state faster than a new node.
+ * The timeout is in place to provide a grace period in which nodes can be brought back to active
+ * if they were deactivated in error. As inactive nodes retain their state
+ * they can be brought back to active and correct state faster than a new node.
*
* Nodes with following flags set are not reusable and will be moved to parked
* instead of dirty:
@@ -44,11 +39,7 @@ public class InactiveExpirer extends Expirer {
@Override
protected void expire(List<Node> expired) {
expired.forEach(node -> {
- if (node.status().wantToDeprovision() || retiredByOperator(node)) {
- nodeRepository.park(node.hostname(), false, Agent.InactiveExpirer, "Expired by InactiveExpirer");
- } else {
- nodeRepository.setDirty(node, Agent.InactiveExpirer, "Expired by InactiveExpirer");
- }
+ nodeRepository.deallocate(node, Agent.InactiveExpirer, "Expired by InactiveExpirer");
});
}
@@ -58,11 +49,4 @@ public class InactiveExpirer extends Expirer {
|| node.allocation().get().owner().instance().isTester();
}
- private static boolean retiredByOperator(Node node) {
- return node.status().wantToRetire() && node.history().event(History.Event.Type.wantToRetire)
- .map(History.Event::agent)
- .map(agent -> agent == Agent.operator)
- .orElse(false);
- }
-
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceDeployment.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceDeployment.java
index 66ddf7f9ffe..52c487c28cf 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceDeployment.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceDeployment.java
@@ -186,7 +186,7 @@ class MaintenanceDeployment implements Closeable {
// Immediately clean up if we reserved the node but could not activate or reserved a node on the wrong host
expectedNewNode.flatMap(node -> nodeRepository.getNode(node.hostname(), Node.State.reserved))
- .ifPresent(node -> nodeRepository.setDirty(node, agent, "Expired by " + agent));
+ .ifPresent(node -> nodeRepository.deallocate(node, agent, "Expired by " + agent));
}
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirer.java
index c6427123d09..1967615de02 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirer.java
@@ -25,6 +25,6 @@ public class ReservationExpirer extends Expirer {
}
@Override
- protected void expire(List<Node> expired) { nodeRepository().setDirty(expired, Agent.ReservationExpirer, "Expired by ReservationExpirer"); }
+ protected void expire(List<Node> expired) { nodeRepository().deallocate(expired, Agent.ReservationExpirer, "Expired by ReservationExpirer"); }
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java
index 9b5dac49e32..08bfd104863 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java
@@ -142,7 +142,7 @@ public class NodesV2ApiHandler extends LoggingRequestHandler {
return new MessageResponse("Moved " + hostnamesAsString(parkedNodes) + " to parked");
}
else if (path.startsWith("/nodes/v2/state/dirty/")) {
- List<Node> dirtiedNodes = nodeRepository.dirtyRecursively(lastElement(path), Agent.operator, "Dirtied through the nodes/v2 API");
+ List<Node> dirtiedNodes = nodeRepository.deallocateRecursively(lastElement(path), Agent.operator, "Dirtied through the nodes/v2 API");
return new MessageResponse("Moved " + hostnamesAsString(dirtiedNodes) + " to dirty");
}
else if (path.startsWith("/nodes/v2/state/active/")) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
index 1f8f3d32043..cf1ccf9a052 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
@@ -142,11 +142,11 @@ public class MockNodeRepository extends NodeRepository {
nodes = addNodes(nodes, Agent.system);
nodes.remove(node7);
nodes.remove(node55);
- nodes = setDirty(nodes, Agent.system, getClass().getSimpleName());
+ nodes = deallocate(nodes, Agent.system, getClass().getSimpleName());
setReady(nodes, Agent.system, getClass().getSimpleName());
fail(node5.hostname(), Agent.system, getClass().getSimpleName());
- dirtyRecursively(node55.hostname(), Agent.system, getClass().getSimpleName());
+ deallocateRecursively(node55.hostname(), Agent.system, getClass().getSimpleName());
fail("dockerhost6.yahoo.com", Agent.operator, getClass().getSimpleName());
removeRecursively("dockerhost6.yahoo.com");
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java
index bcf53a07490..5f357b9e4b0 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java
@@ -228,12 +228,12 @@ public class NodeRepositoryTest {
assertEquals(6, tester.nodeRepository().getNodes().size());
// Should be OK to dirty host2 as it is in provisioned and its only child is in failed
- tester.nodeRepository().dirtyRecursively("host2", Agent.system, NodeRepositoryTest.class.getSimpleName());
+ tester.nodeRepository().deallocateRecursively("host2", Agent.system, NodeRepositoryTest.class.getSimpleName());
assertEquals(asSet("host2", "node20"), filterNodes(tester, node -> node.state() == Node.State.dirty));
// Cant dirty host1, node11 is ready and node12 is active
try {
- tester.nodeRepository().dirtyRecursively("host1", Agent.system, NodeRepositoryTest.class.getSimpleName());
+ tester.nodeRepository().deallocateRecursively("host1", Agent.system, NodeRepositoryTest.class.getSimpleName());
fail("Should not be able to dirty host1");
} catch (IllegalArgumentException ignored) { } // Expected;
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
index 940404fa605..9f2f7541c91 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
@@ -312,7 +312,7 @@ public class FailedExpirerTest {
List<Node> nodes = Stream.of(hostname)
.map(this::get)
.collect(Collectors.toList());
- nodes = nodeRepository.setDirty(nodes, Agent.system, getClass().getSimpleName());
+ nodes = nodeRepository.deallocate(nodes, Agent.system, getClass().getSimpleName());
nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
return this;
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java
index 8bcf1552204..60ca625d07e 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirerTest.java
@@ -60,12 +60,6 @@ public class LoadBalancerExpirerTest {
// Expirer defers removal while nodes are still allocated to application
expirer.maintain();
- assertEquals(3, tester.loadBalancerService().instances().size());
- assertEquals(2, tester.loadBalancerService().instances().get(lb1).reals().size());
- dirtyNodesOf(app1, cluster1);
-
- // Expirer prunes reals before expiration time of load balancer itself
- expirer.maintain();
assertEquals(Set.of(), tester.loadBalancerService().instances().get(lb1).reals());
assertEquals(Set.of(), loadBalancers.get().get(lb1).instance().reals());
@@ -138,11 +132,11 @@ public class LoadBalancerExpirerTest {
}
private void dirtyNodesOf(ApplicationId application, ClusterSpec.Id cluster) {
- tester.nodeRepository().setDirty(tester.nodeRepository().getNodes(application).stream()
- .filter(node -> node.allocation().isPresent())
- .filter(node -> node.allocation().get().membership().cluster().id().equals(cluster))
- .collect(Collectors.toList()),
- Agent.system, this.getClass().getSimpleName());
+ tester.nodeRepository().deallocate(tester.nodeRepository().getNodes(application).stream()
+ .filter(node -> node.allocation().isPresent())
+ .filter(node -> node.allocation().get().membership().cluster().id().equals(cluster))
+ .collect(Collectors.toList()),
+ Agent.system, this.getClass().getSimpleName());
}
private void deployApplication(ApplicationId application, ClusterSpec.Id... clusters) {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java
index c03e489def2..9adba744101 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java
@@ -163,7 +163,7 @@ public class MetricsReporterTest {
Node dockerHost = Node.create("openStackId1", new IP.Config(Set.of("::1"), ipAddressPool), "dockerHost",
nodeFlavors.getFlavorOrThrow("host"), NodeType.host).build();
nodeRepository.addNodes(List.of(dockerHost), Agent.system);
- nodeRepository.dirtyRecursively("dockerHost", Agent.system, getClass().getSimpleName());
+ nodeRepository.deallocateRecursively("dockerHost", Agent.system, getClass().getSimpleName());
nodeRepository.setReady("dockerHost", Agent.system, getClass().getSimpleName());
Node container1 = Node.createDockerNode(Set.of("::2"), "container1",
@@ -225,7 +225,7 @@ public class MetricsReporterTest {
ApplicationId application = ApplicationId.from("t1", "a1", "default");
Map<String, String> dimensions = Map.of("applicationId", application.toFullString());
NodeResources resources = new NodeResources(2, 8, 100, 1);
- List<Node> activeNodes = tester.deploy(application, Capacity.from(new ClusterResources(4, 1, resources)));
+ List<Node> activeNodes = tester.deploy(application, ProvisioningTester.contentClusterSpec(), Capacity.from(new ClusterResources(4, 1, resources)));
metricsReporter.maintain();
assertEquals(0D, getMetric("nodes.nonActiveFraction", metric, dimensions));
assertEquals(4, getMetric("nodes.active", metric, dimensions));
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
index 12b682ae26c..d0473d08ea2 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
@@ -259,7 +259,7 @@ public class NodeFailTester {
}
nodes = nodeRepository.addNodes(nodes, Agent.system);
- nodes = nodeRepository.setDirty(nodes, Agent.system, getClass().getSimpleName());
+ nodes = nodeRepository.deallocate(nodes, Agent.system, getClass().getSimpleName());
return nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
}
@@ -267,7 +267,7 @@ public class NodeFailTester {
List<Node> nodes = tester.makeProvisionedNodes(count, (index) -> "parent" + index,
hostFlavors.getFlavorOrThrow("default"),
Optional.empty(), NodeType.host, 10, false);
- nodes = nodeRepository.setDirty(nodes, Agent.system, getClass().getSimpleName());
+ nodes = nodeRepository.deallocate(nodes, Agent.system, getClass().getSimpleName());
tester.activateTenantHosts();
return nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java
index cd188bc017f..ae1e36d73dd 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java
@@ -96,10 +96,10 @@ public class PeriodicApplicationMaintainerTest {
// Cause maintenance deployment which will update the applications with the re-activated nodes
clock.advance(Duration.ofMinutes(35)); // Otherwise redeploys are inhibited
fixture.runApplicationMaintainer();
- assertEquals("Superflous content nodes are retired",
+ assertEquals("Superfluous content nodes are retired",
reactivatedInApp2, fixture.getNodes(Node.State.active).retired().size());
- assertEquals("Superflous container nodes are deactivated (this makes little point for container nodes)",
- reactivatedInApp1, fixture.getNodes(Node.State.inactive).size());
+ assertEquals("Superfluous container nodes are deallocated",
+ reactivatedInApp1, fixture.getNodes(Node.State.dirty).size());
}
@Test(timeout = 60_000)
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
index 0246ec524e3..ef0e6d4ddc4 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.provisioning;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.ApplicationTransaction;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
@@ -14,11 +15,14 @@ import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.OutOfCapacityException;
+import com.yahoo.config.provision.ProvisionLock;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
+import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
+import com.yahoo.vespa.hosted.provision.node.Agent;
import org.junit.Test;
import java.util.HashSet;
@@ -356,11 +360,15 @@ public class DockerProvisioningTest {
tester.makeReadyHosts(5, r).activateTenantHosts();
ApplicationId app1 = ProvisioningTester.applicationId("app1");
- ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.container, new ClusterSpec.Id("cluster1")).vespaVersion("7").build();
+ ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.content, new ClusterSpec.Id("cluster1")).vespaVersion("7").build();
tester.activate(app1, cluster1, Capacity.from(new ClusterResources(5, 1, r)));
tester.activate(app1, cluster1, Capacity.from(new ClusterResources(2, 1, r)));
+ var tx = new ApplicationTransaction(new ProvisionLock(app1, tester.nodeRepository().lock(app1)), new NestedTransaction());
+ tester.nodeRepository().deactivate(tester.nodeRepository().list(app1, Node.State.active).retired().asList(), tx);
+ tx.nested().commit();
+
assertEquals(2, tester.getNodes(app1, Node.State.active).size());
assertEquals(3, tester.getNodes(app1, Node.State.inactive).size());
@@ -372,16 +380,16 @@ public class DockerProvisioningTest {
}
@Test
- public void inactive_container_nodes_are_reused() {
- assertInactiveReuse(ClusterSpec.Type.container);
+ public void inactive_container_nodes_are_not_reused() {
+ assertInactiveReuse(ClusterSpec.Type.container, false);
}
@Test
public void inactive_content_nodes_are_reused() {
- assertInactiveReuse(ClusterSpec.Type.content);
+ assertInactiveReuse(ClusterSpec.Type.content, true);
}
- private void assertInactiveReuse(ClusterSpec.Type clusterType) {
+ private void assertInactiveReuse(ClusterSpec.Type clusterType, boolean expectedReuse) {
NodeResources r = new NodeResources(20, 40, 100, 4);
ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east")))
.flavors(List.of(new Flavor(r)))
@@ -398,9 +406,18 @@ public class DockerProvisioningTest {
tester.nodeRepository().setRemovable(app1, tester.getNodes(app1).retired().asList());
tester.activate(app1, cluster1, Capacity.from(new ClusterResources(2, 1, r)));
- assertEquals(2, tester.getNodes(app1, Node.State.inactive).size());
- tester.activate(app1, cluster1, Capacity.from(new ClusterResources(4, 1, r)));
- assertEquals(0, tester.getNodes(app1, Node.State.inactive).size());
+ if (expectedReuse) {
+ assertEquals(2, tester.getNodes(app1, Node.State.inactive).size());
+ tester.activate(app1, cluster1, Capacity.from(new ClusterResources(4, 1, r)));
+ assertEquals(0, tester.getNodes(app1, Node.State.inactive).size());
+ }
+ else {
+ assertEquals(0, tester.getNodes(app1, Node.State.inactive).size());
+ assertEquals(2, tester.nodeRepository().getNodes(Node.State.dirty).size());
+ tester.nodeRepository().setReady(tester.nodeRepository().getNodes(Node.State.dirty), Agent.system, "test");
+ tester.activate(app1, cluster1, Capacity.from(new ClusterResources(4, 1, r)));
+ }
+
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
index eef342b527b..105f2122e0c 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
@@ -263,7 +263,7 @@ public class LoadBalancerProvisionerTest {
}
private void dirtyNodesOf(ApplicationId application) {
- tester.nodeRepository().setDirty(tester.nodeRepository().getNodes(application), Agent.system, this.getClass().getSimpleName());
+ tester.nodeRepository().deallocate(tester.nodeRepository().getNodes(application), Agent.system, this.getClass().getSimpleName());
}
private Set<HostSpec> prepare(ApplicationId application, ClusterSpec... specs) {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java
index acf7bda3cbf..bf2a6ba3627 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java
@@ -153,7 +153,7 @@ public class NodeTypeProvisioningTest {
assertEquals(10, nodes.size());
// Verify that the node is now inactive
- assertEquals(Node.State.inactive, tester.nodeRepository().getNode(nodeToRetire.hostname())
+ assertEquals(Node.State.dirty, tester.nodeRepository().getNode(nodeToRetire.hostname())
.orElseThrow(RuntimeException::new).state());
}
}
@@ -232,7 +232,7 @@ public class NodeTypeProvisioningTest {
assertEquals(10, nodes.size());
// Verify the node we previously set to retire has finished retiring
- assertEquals(Node.State.inactive, tester.nodeRepository().getNode(currentyRetiringHostname)
+ assertEquals(Node.State.dirty, tester.nodeRepository().getNode(currentyRetiringHostname)
.orElseThrow(RuntimeException::new).state());
// Verify that a node is currently retiring
@@ -263,9 +263,9 @@ public class NodeTypeProvisioningTest {
.count();
assertEquals(11 - numNodesToRetire, numRetiredActiveProxyNodes);
- // All the nodes that were marked with wantToRetire earlier are now inactive
+ // All the nodes that were marked with wantToRetire earlier are now dirty
assertEquals(nodesToRetire.stream().map(Node::hostname).collect(Collectors.toSet()),
- tester.nodeRepository().getNodes(Node.State.inactive).stream().map(Node::hostname).collect(Collectors.toSet()));
+ tester.nodeRepository().getNodes(Node.State.dirty).stream().map(Node::hostname).collect(Collectors.toSet()));
}
private List<HostSpec> deployProxies(ApplicationId application, ProvisioningTester tester) {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
index 611c3839f56..7778077110a 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
@@ -32,6 +32,7 @@ import com.yahoo.vespa.service.duper.InfraApplication;
import org.junit.Test;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -100,14 +101,15 @@ public class ProvisioningTest {
SystemState state6 = prepare(application1, 0, 2, 0, 3, defaultResources, tester);
tester.activate(application1, state6.allHosts);
assertEquals(5, tester.getNodes(application1, Node.State.active).size());
- assertEquals(5, tester.getNodes(application1, Node.State.inactive).size());
+ assertEquals(3, tester.getNodes(application1, Node.State.inactive).size());
// delete app
NodeList previouslyActive = tester.getNodes(application1, Node.State.active);
NodeList previouslyInactive = tester.getNodes(application1, Node.State.inactive);
tester.remove(application1);
- assertEquals(tester.toHostNames(previouslyActive.asList()), tester.toHostNames(tester.nodeRepository().getNodes(application1, Node.State.inactive)));
- assertEquals(tester.toHostNames(previouslyInactive.asList()), tester.toHostNames(tester.nodeRepository().getNodes(Node.State.dirty)));
+ assertEquals(tester.toHostNames(previouslyActive.not().container().asList()),
+ tester.toHostNames(tester.nodeRepository().getNodes(application1, Node.State.inactive)));
+ assertTrue(tester.nodeRepository().getNodes(Node.State.dirty).containsAll(previouslyActive.container().asList()));
assertEquals(0, tester.getNodes(application1, Node.State.active).size());
assertTrue(tester.nodeRepository().applications().get(application1).isEmpty());
@@ -203,7 +205,7 @@ public class ProvisioningTest {
ApplicationId application1 = ProvisioningTester.applicationId();
- tester.makeReadyHosts(24, defaultResources);
+ tester.makeReadyHosts(30, defaultResources);
tester.activateTenantHosts();
// deploy
@@ -219,8 +221,8 @@ public class ProvisioningTest {
// decrease again
SystemState state3 = prepare(application1, 2, 2, 3, 3, defaultResources, tester);
tester.activate(application1, state3.allHosts);
- assertEquals("Superfluous container nodes are deactivated",
- 3-2 + 4-2, tester.getNodes(application1, Node.State.inactive).size());
+ assertEquals("Superfluous container nodes are dirtyed",
+ 3-2 + 4-2, tester.nodeRepository().getNodes(Node.State.dirty).size());
assertEquals("Superfluous content nodes are retired",
4-3 + 5-3, tester.getNodes(application1, Node.State.active).retired().size());
@@ -229,7 +231,6 @@ public class ProvisioningTest {
assertEquals("Inactive nodes are reused", 0, tester.getNodes(application1, Node.State.inactive).size());
assertEquals("Earlier retired nodes are not unretired before activate",
4-3 + 5-3, tester.getNodes(application1, Node.State.active).retired().size());
- state4.assertExtends(state2);
assertEquals("New and inactive nodes are reserved", 4 + 3, tester.getNodes(application1, Node.State.reserved).size());
// Remove a retired host from one of the content clusters (which one is random depending on host names)
HostSpec removed = state4.removeHost(tester.getNodes(application1, Node.State.active).retired().asList().get(0).hostname());
@@ -243,8 +244,8 @@ public class ProvisioningTest {
// decrease again
SystemState state5 = prepare(application1, 2, 2, 3, 3, defaultResources, tester);
tester.activate(application1, state5.allHosts);
- assertEquals("Superfluous container nodes are also deactivated",
- 4-2 + 5-2 + 1, tester.getNodes(application1, Node.State.inactive).size()); //
+ assertEquals("Superfluous container nodes are also dirtyed",
+ 4-2 + 5-2 + 1 + 4-2, tester.nodeRepository().getNodes(Node.State.dirty).size());
assertEquals("Superfluous content nodes are retired",
5-3 + 6-3 - 1, tester.getNodes(application1, Node.State.active).retired().size());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
index ebd856e96a0..7c43a8d5859 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
@@ -456,7 +456,7 @@ public class ProvisioningTester {
}
nodes = nodeRepository.addNodes(nodes, Agent.system);
- nodes = nodeRepository.setDirty(nodes, Agent.system, getClass().getSimpleName());
+ nodes = nodeRepository.deallocate(nodes, Agent.system, getClass().getSimpleName());
nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
ConfigServerApplication application = new ConfigServerApplication();
@@ -482,7 +482,7 @@ public class ProvisioningTester {
}
public List<Node> makeReadyNodes(int n, Flavor flavor, Optional<TenantName> reservedTo, NodeType type, int ipAddressPoolSize, boolean dualStack) {
List<Node> nodes = makeProvisionedNodes(n, flavor, reservedTo, type, ipAddressPoolSize, dualStack);
- nodes = nodeRepository.setDirty(nodes, Agent.system, getClass().getSimpleName());
+ nodes = nodeRepository.deallocate(nodes, Agent.system, getClass().getSimpleName());
return nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
}
@@ -523,7 +523,7 @@ public class ProvisioningTester {
nodes.add(builder.build());
}
nodes = nodeRepository.addNodes(nodes, Agent.system);
- nodes = nodeRepository.setDirty(nodes, Agent.system, getClass().getSimpleName());
+ nodes = nodeRepository.deallocate(nodes, Agent.system, getClass().getSimpleName());
nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
return nodes;
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node55.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node55.json
index 3107311b792..0a2bf95c5bd 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node55.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node55.json
@@ -1,14 +1,14 @@
{
"url": "http://localhost:8080/nodes/v2/node/host55.yahoo.com",
"id": "host55.yahoo.com",
- "state": "dirty",
+ "state": "parked",
"type": "tenant",
"hostname": "host55.yahoo.com",
"openStackId": "node55",
"flavor": "[vcpu: 2.0, memory: 8.0 Gb, disk 50.0 Gb, bandwidth: 1.0 Gbps, storage type: local]",
"resources":{"vcpu":2.0,"memoryGb":8.0,"diskGb":50.0,"bandwidthGbps":1.0,"diskSpeed":"fast","storageType":"local"},
"environment": "DOCKER_CONTAINER",
- "rebootGeneration": 1,
+ "rebootGeneration": 0,
"currentRebootGeneration": 0,
"failCount": 0,
"wantToRetire": true,
@@ -20,7 +20,7 @@
"agent": "system"
},
{
- "event": "deallocated",
+ "event": "parked",
"at": 123,
"agent": "system"
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes-recursive-include-deprovisioned.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes-recursive-include-deprovisioned.json
index 2b650bad39b..03df6c8e1dc 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes-recursive-include-deprovisioned.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes-recursive-include-deprovisioned.json
@@ -17,8 +17,8 @@
@include(node2.json),
@include(docker-node1.json),
@include(node1.json),
- @include(node55.json),
@include(node5.json),
+ @include(node55.json),
@include(dockerhost6.json)
]
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes-recursive.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes-recursive.json
index 55e216f454a..8835945dc92 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes-recursive.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes-recursive.json
@@ -17,7 +17,7 @@
@include(node2.json),
@include(docker-node1.json),
@include(node1.json),
- @include(node55.json),
- @include(node5.json)
+ @include(node5.json),
+ @include(node55.json)
]
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes.json
index 54ff2bc232f..db4d0bb1682 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/nodes.json
@@ -52,10 +52,10 @@
"url": "http://localhost:8080/nodes/v2/node/host1.yahoo.com"
},
{
- "url": "http://localhost:8080/nodes/v2/node/host55.yahoo.com"
+ "url": "http://localhost:8080/nodes/v2/node/host5.yahoo.com"
},
{
- "url": "http://localhost:8080/nodes/v2/node/host5.yahoo.com"
+ "url": "http://localhost:8080/nodes/v2/node/host55.yahoo.com"
}
]
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/states-recursive.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/states-recursive.json
index 27767be6315..68ff9fedc00 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/states-recursive.json
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/states-recursive.json
@@ -45,7 +45,6 @@
"dirty": {
"url": "http://localhost:8080/nodes/v2/state/dirty",
"nodes": [
- @include(node55.json)
]
},
"failed": {
@@ -57,6 +56,7 @@
"parked": {
"url": "http://localhost:8080/nodes/v2/state/parked",
"nodes": [
+ @include(node55.json)
]
},
"deprovisioned": {
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
index e7f74161a8e..65744815ecf 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
@@ -55,6 +55,7 @@ import com.yahoo.jdisc.handler.ResponseHandler;
import com.yahoo.jdisc.handler.UnsafeContentInputStream;
import com.yahoo.jdisc.http.HttpRequest;
import com.yahoo.jdisc.http.HttpRequest.Method;
+import com.yahoo.messagebus.DynamicThrottlePolicy;
import com.yahoo.messagebus.Message;
import com.yahoo.messagebus.StaticThrottlePolicy;
import com.yahoo.messagebus.Trace;
@@ -148,10 +149,12 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private static final String FIELD_SET = "fieldSet";
private static final String SELECTION = "selection";
private static final String CLUSTER = "cluster";
+ private static final String DESTINATION_CLUSTER = "destinationCluster";
private static final String CONTINUATION = "continuation";
private static final String WANTED_DOCUMENT_COUNT = "wantedDocumentCount";
private static final String CONCURRENCY = "concurrency";
private static final String BUCKET_SPACE = "bucketSpace";
+ private static final String TIME_CHUNK = "timeChunk";
private static final String TIMEOUT = "timeout";
private static final String TRACELEVEL = "tracelevel";
@@ -347,7 +350,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private ContentChannel getDocuments(HttpRequest request, DocumentPath path, ResponseHandler handler) {
enqueueAndDispatch(request, handler, () -> {
- VisitorParameters parameters = parseParameters(request, path);
+ VisitorParameters parameters = parseGetParameters(request, path);
return () -> {
visitAndWrite(request, parameters, handler);
return true; // VisitorSession has its own throttle handling.
@@ -358,8 +361,10 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private ContentChannel postDocuments(HttpRequest request, DocumentPath path, ResponseHandler handler) {
enqueueAndDispatch(request, handler, () -> {
+ StorageCluster destination = resolveCluster(Optional.of(requireProperty(request, DESTINATION_CLUSTER)), clusters);
VisitorParameters parameters = parseParameters(request, path);
- parameters.setRemoteDataHandler(getProperty(request, ROUTE).orElseThrow(() -> new IllegalArgumentException("Missing required property '" + ROUTE + "'")));
+ parameters.setRemoteDataHandler("[Content:cluster=" + destination.name() + "]"); // Bypass indexing.
+ parameters.setFieldSet(AllFields.NAME);
return () -> {
visitWithRemote(request, parameters, handler);
return true; // VisitorSession has its own throttle handling.
@@ -369,19 +374,17 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
}
private ContentChannel putDocuments(HttpRequest request, DocumentPath path, ResponseHandler handler) {
- if (getProperty(request, SELECTION).isEmpty())
- throw new IllegalArgumentException("Missing required property '" + SELECTION + "'");
-
return new ForwardingContentChannel(in -> {
enqueueAndDispatch(request, handler, () -> {
- String type = path.documentType().orElseThrow(() -> new IllegalStateException("Document type must be specified for mass updates"));
- IdIdString dummyId = new IdIdString("dummy", type, "", "");
+ StorageCluster cluster = resolveCluster(Optional.of(requireProperty(request, CLUSTER)), clusters);
VisitorParameters parameters = parseParameters(request, path);
parameters.setFieldSet(DocIdOnly.NAME);
+ String type = path.documentType().orElseThrow(() -> new IllegalStateException("Document type must be specified for mass updates"));
+ IdIdString dummyId = new IdIdString("dummy", type, "", "");
DocumentUpdate update = parser.parseUpdate(in, dummyId.toString());
- update.setCondition(new TestAndSetCondition(parameters.getDocumentSelection()));
+ update.setCondition(new TestAndSetCondition(requireProperty(request, SELECTION)));
return () -> {
- visitAndUpdate(request, parameters, handler, update, getProperty(request, ROUTE));
+ visitAndUpdate(request, parameters, handler, update, cluster.name());
return true; // VisitorSession has its own throttle handling.
};
});
@@ -389,15 +392,13 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
}
private ContentChannel deleteDocuments(HttpRequest request, DocumentPath path, ResponseHandler handler) {
- if (getProperty(request, SELECTION).isEmpty())
- throw new IllegalArgumentException("Missing required property '" + SELECTION + "'");
-
enqueueAndDispatch(request, handler, () -> {
VisitorParameters parameters = parseParameters(request, path);
parameters.setFieldSet(DocIdOnly.NAME);
- TestAndSetCondition condition = new TestAndSetCondition(parameters.getDocumentSelection());
+ TestAndSetCondition condition = new TestAndSetCondition(requireProperty(request, SELECTION));
+ StorageCluster cluster = resolveCluster(Optional.of(requireProperty(request, CLUSTER)), clusters);
return () -> {
- visitAndDelete(request, parameters, handler, condition, getProperty(request, ROUTE));
+ visitAndDelete(request, parameters, handler, condition, cluster.name());
return true; // VisitorSession has its own throttle handling.
};
});
@@ -478,7 +479,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
.orElse(parameters());
for (String name : names) switch (name) {
case CLUSTER:
- parameters = getProperty(request, CLUSTER).map(cluster -> resolveCluster(Optional.of(cluster), clusters).route())
+ parameters = getProperty(request, CLUSTER).map(cluster -> resolveCluster(Optional.of(cluster), clusters).name())
.map(parameters::withRoute)
.orElse(parameters);
break;
@@ -644,6 +645,10 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
json.writeStringField("message", message);
}
+ synchronized void writeDocumentCount(long count) throws IOException {
+ json.writeNumberField("documentCount", count);
+ }
+
synchronized void writeDocId(DocumentId id) throws IOException {
json.writeStringField("id", id.toString());
}
@@ -916,10 +921,10 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
// ------------------------------------------------- Visits ------------------------------------------------
- private VisitorParameters parseParameters(HttpRequest request, DocumentPath path) {
+ private VisitorParameters parseGetParameters(HttpRequest request, DocumentPath path) {
int wantedDocumentCount = Math.min(1 << 10, getProperty(request, WANTED_DOCUMENT_COUNT, integerParser).orElse(1));
if (wantedDocumentCount <= 0)
- throw new IllegalArgumentException("wantedDocumentCount must be positive");
+ throw new IllegalArgumentException("wantedDocumentCount must be positive");
int concurrency = Math.min(100, getProperty(request, CONCURRENCY, integerParser).orElse(1));
if (concurrency <= 0)
@@ -929,6 +934,25 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
if (cluster.isEmpty() && path.documentType().isEmpty())
throw new IllegalArgumentException("Must set 'cluster' parameter to a valid content cluster id when visiting at a root /document/v1/ level");
+ VisitorParameters parameters = parseCommonParameters(request, path, cluster);
+ parameters.setFieldSet(getProperty(request, FIELD_SET).orElse(path.documentType().map(type -> type + ":[document]").orElse(AllFields.NAME)));
+ parameters.setMaxTotalHits(wantedDocumentCount);
+ parameters.setThrottlePolicy(new StaticThrottlePolicy().setMaxPendingCount(concurrency));
+ parameters.visitInconsistentBuckets(true);
+ parameters.setSessionTimeoutMs(Math.max(1, request.getTimeout(TimeUnit.MILLISECONDS) - 5000));
+ return parameters;
+ }
+
+ private VisitorParameters parseParameters(HttpRequest request, DocumentPath path) {
+ disallow(request, CONCURRENCY, FIELD_SET, ROUTE, WANTED_DOCUMENT_COUNT);
+ requireProperty(request, SELECTION);
+ VisitorParameters parameters = parseCommonParameters(request, path, Optional.of(requireProperty(request, CLUSTER)));
+ parameters.setThrottlePolicy(new DynamicThrottlePolicy().setMinWindowSize(1).setWindowSizeIncrement(1));
+ parameters.setSessionTimeoutMs(Math.max(1, getProperty(request, TIME_CHUNK, timeoutMillisParser).orElse(60_000L)));
+ return parameters;
+ }
+
+ private VisitorParameters parseCommonParameters(HttpRequest request, DocumentPath path, Optional<String> cluster) {
VisitorParameters parameters = new VisitorParameters(Stream.of(getProperty(request, SELECTION),
path.documentType(),
path.namespace().map(value -> "id.namespace=='" + value + "'"),
@@ -940,15 +964,10 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
.toString());
getProperty(request, CONTINUATION).map(ProgressToken::fromSerializedString).ifPresent(parameters::setResumeToken);
- parameters.setFieldSet(getProperty(request, FIELD_SET).orElse(path.documentType().map(type -> type + ":[document]").orElse(AllFields.NAME)));
- parameters.setMaxTotalHits(wantedDocumentCount);
- parameters.setThrottlePolicy(new StaticThrottlePolicy().setMaxPendingCount(concurrency));
- parameters.setSessionTimeoutMs(Math.max(1, request.getTimeout(TimeUnit.MILLISECONDS) - 5000));
- parameters.visitInconsistentBuckets(true);
parameters.setPriority(DocumentProtocol.Priority.NORMAL_4);
StorageCluster storageCluster = resolveCluster(cluster, clusters);
- parameters.setRoute(storageCluster.route());
+ parameters.setRoute(storageCluster.name());
parameters.setBucketSpace(resolveBucket(storageCluster,
path.documentType(),
List.of(FixedBucketSpaces.defaultSpace(), FixedBucketSpaces.globalSpace()),
@@ -969,7 +988,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
}
private void visitAndDelete(HttpRequest request, VisitorParameters parameters, ResponseHandler handler,
- TestAndSetCondition condition, Optional<String> route) {
+ TestAndSetCondition condition, String route) {
visitAndProcess(request, parameters, handler, route, (id, operationParameters) -> {
DocumentRemove remove = new DocumentRemove(id);
remove.setCondition(condition);
@@ -978,7 +997,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
}
private void visitAndUpdate(HttpRequest request, VisitorParameters parameters, ResponseHandler handler,
- DocumentUpdate protoUpdate, Optional<String> route) {
+ DocumentUpdate protoUpdate, String route) {
visitAndProcess(request, parameters, handler, route, (id, operationParameters) -> {
DocumentUpdate update = new DocumentUpdate(protoUpdate);
update.setId(id);
@@ -987,11 +1006,10 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
}
private void visitAndProcess(HttpRequest request, VisitorParameters parameters, ResponseHandler handler,
- Optional<String> route, BiFunction<DocumentId, DocumentOperationParameters, Result> operation) {
+ String route, BiFunction<DocumentId, DocumentOperationParameters, Result> operation) {
visit(request, parameters, handler, new VisitCallback() {
@Override public void onDocument(JsonResponse response, Document document, Runnable ack, Consumer<String> onError) {
- DocumentOperationParameters operationParameters = (route.isEmpty() ? parameters()
- : parameters().withRoute(route.get()))
+ DocumentOperationParameters operationParameters = parameters().withRoute(route)
.withResponseHandler(operationResponse -> {
outstanding.decrementAndGet();
switch (operationResponse.outcome()) {
@@ -1057,7 +1075,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
callback.onEnd(response);
switch (code) {
case TIMEOUT:
- if ( ! hasVisitedAnyBuckets()) {
+ if ( ! hasVisitedAnyBuckets() && parameters.getVisitInconsistentBuckets()) {
response.writeMessage("No buckets visited within timeout of " +
parameters.getSessionTimeoutMs() + "ms (request timeout -5s)");
response.respond(Response.Status.GATEWAY_TIMEOUT);
@@ -1066,14 +1084,21 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
case SUCCESS: // Intentional fallthrough.
case ABORTED: // Intentional fallthrough.
if (error.get() == null) {
- if (getProgress() != null && ! getProgress().isFinished())
- response.writeContinuation(getProgress().serializeToString());
+ ProgressToken progress = getProgress() != null ? getProgress() : parameters.getResumeToken();
+ if (progress != null && ! progress.isFinished())
+ response.writeContinuation(progress.serializeToString());
+
+ if (getVisitorStatistics() != null)
+ response.writeDocumentCount(getVisitorStatistics().getDocumentsVisited());
response.respond(Response.Status.OK);
break;
}
default:
response.writeMessage(error.get() != null ? error.get() : message != null ? message : "Visiting failed");
+ if (getVisitorStatistics() != null)
+ response.writeDocumentCount(getVisitorStatistics().getDocumentsReturned());
+
response.respond(Response.Status.INTERNAL_SERVER_ERROR);
}
visitDispatcher.execute(() -> {
@@ -1113,6 +1138,11 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
// ------------------------------------------------ Helpers ------------------------------------------------
+ private static String requireProperty(HttpRequest request, String name) {
+ return getProperty(request, name)
+ .orElseThrow(() -> new IllegalArgumentException("Must specify '" + name + "' at '" + request.getUri().getRawPath() + "'"));
+ }
+
/** Returns the last property with the given name, if present, or throws if this is empty or blank. */
private static Optional<String> getProperty(HttpRequest request, String name) {
if ( ! request.parameters().containsKey(name))
@@ -1130,6 +1160,12 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
return getProperty(request, name).map(parser::parse);
}
+ private static void disallow(HttpRequest request, String... properties) {
+ for (String property : properties)
+ if (request.parameters().containsKey(property))
+ throw new IllegalArgumentException("May not specify '" + property + "' at '" + request.getUri().getRawPath() + "'");
+ }
+
@FunctionalInterface
interface Parser<T> extends Function<String, T> {
default T parse(String value) {
@@ -1177,7 +1213,6 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
}
String name() { return name; }
- String route() { return "[Content:cluster=" + name() + "]"; }
Optional<String> bucketOf(String documentType) { return Optional.ofNullable(documentBuckets.get(documentType)); }
}
diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java
index 1e3f3e13c2a..5d81e00cbcb 100644
--- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java
+++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java
@@ -142,8 +142,8 @@ public class DocumentV1ApiTest {
public void testResolveCluster() {
assertEquals("content",
DocumentV1ApiHandler.resolveCluster(Optional.empty(), clusters).name());
- assertEquals("[Content:cluster=content]",
- DocumentV1ApiHandler.resolveCluster(Optional.of("content"), clusters).route());
+ assertEquals("content",
+ DocumentV1ApiHandler.resolveCluster(Optional.of("content"), clusters).name());
try {
DocumentV1ApiHandler.resolveCluster(Optional.empty(), Map.of());
fail("Should fail without any clusters");
@@ -198,7 +198,7 @@ public class DocumentV1ApiTest {
// GET at root is a visit. Numeric parameters have an upper bound.
access.expect(tokens);
access.expect(parameters -> {
- assertEquals("[Content:cluster=content]", parameters.getRoute().toString());
+ assertEquals("content", parameters.getRoute().toString());
assertEquals("default", parameters.getBucketSpace());
assertEquals(1024, parameters.getMaxTotalHits());
assertEquals(100, ((StaticThrottlePolicy) parameters.getThrottlePolicy()).getMaxPendingCount());
@@ -211,6 +211,7 @@ public class DocumentV1ApiTest {
parameters.getLocalDataHandler().onMessage(new PutDocumentMessage(new DocumentPut(doc3)), tokens.get(2));
VisitorStatistics statistics = new VisitorStatistics();
statistics.setBucketsVisited(1);
+ statistics.setDocumentsVisited(3);
parameters.getControlHandler().onVisitorStatistics(statistics);
parameters.getControlHandler().onDone(VisitorControlHandler.CompletionCode.TIMEOUT, "timeout is OK");
});
@@ -235,7 +236,8 @@ public class DocumentV1ApiTest {
" \"id\": \"id:space:music:g=a:three\"," +
" \"fields\": {}" +
" }" +
- " ]" +
+ " ]," +
+ " \"documentCount\": 3" +
"}", response.readAll());
assertEquals(200, response.getStatus());
@@ -252,24 +254,24 @@ public class DocumentV1ApiTest {
"}", response.readAll());
assertEquals(400, response.getStatus());
- // POST with namespace and document type is a restricted visit with a required remote data handler ("route")
+ // POST with namespace and document type is a restricted visit with a required destination cluster ("destinationCluster")
access.expect(parameters -> {
fail("Not supposed to run");
});
response = driver.sendRequest("http://localhost/document/v1/space/music/docid", POST);
assertSameJson("{" +
" \"pathId\": \"/document/v1/space/music/docid\"," +
- " \"message\": \"Missing required property 'route'\"" +
+ " \"message\": \"Must specify 'destinationCluster' at '/document/v1/space/music/docid'\"" +
"}", response.readAll());
assertEquals(400, response.getStatus());
- // POST with namespace and document type is a restricted visit with a require remote data handler ("route")
+ // POST with namespace and document type is a restricted visit with a required destination cluster ("destinationCluster")
access.expect(parameters -> {
- assertEquals("zero", parameters.getRemoteDataHandler());
- assertEquals("music:[document]", parameters.fieldSet());
+ assertEquals("[Content:cluster=content]", parameters.getRemoteDataHandler());
+ assertEquals("[all]", parameters.fieldSet());
parameters.getControlHandler().onDone(VisitorControlHandler.CompletionCode.SUCCESS, "We made it!");
});
- response = driver.sendRequest("http://localhost/document/v1/space/music/docid?route=zero", POST);
+ response = driver.sendRequest("http://localhost/document/v1/space/music/docid?destinationCluster=content&selection=true&cluster=content", POST);
assertSameJson("{" +
" \"pathId\": \"/document/v1/space/music/docid\"" +
"}", response.readAll());
@@ -280,19 +282,20 @@ public class DocumentV1ApiTest {
access.expect(parameters -> {
assertEquals("(true) and (music) and (id.namespace=='space')", parameters.getDocumentSelection());
assertEquals("[id]", parameters.fieldSet());
+ assertEquals(10_000, parameters.getSessionTimeoutMs());
parameters.getLocalDataHandler().onMessage(new PutDocumentMessage(new DocumentPut(doc3)), tokens.get(2));
- parameters.getControlHandler().onDone(VisitorControlHandler.CompletionCode.SUCCESS, "Huzzah!");
+ parameters.getControlHandler().onDone(VisitorControlHandler.CompletionCode.TIMEOUT, "Won't care");
});
access.session.expect((update, parameters) -> {
DocumentUpdate expectedUpdate = new DocumentUpdate(doc3.getDataType(), doc3.getId());
expectedUpdate.addFieldUpdate(FieldUpdate.createAssign(doc3.getField("artist"), new StringFieldValue("Lisa Ekdahl")));
- expectedUpdate.setCondition(new TestAndSetCondition("(true) and (music) and (id.namespace=='space')"));
+ expectedUpdate.setCondition(new TestAndSetCondition("true"));
assertEquals(expectedUpdate, update);
parameters.responseHandler().get().handleResponse(new UpdateResponse(0, false));
- assertEquals(parameters().withRoute("zero"), parameters);
+ assertEquals(parameters().withRoute("content"), parameters);
return new Result(Result.ResultType.SUCCESS, null);
});
- response = driver.sendRequest("http://localhost/document/v1/space/music/docid?selection=true&route=zero", PUT,
+ response = driver.sendRequest("http://localhost/document/v1/space/music/docid?selection=true&cluster=content&timeChunk=10", PUT,
"{" +
" \"fields\": {" +
" \"artist\": { \"assign\": \"Lisa Ekdahl\" }" +
@@ -303,14 +306,25 @@ public class DocumentV1ApiTest {
"}", response.readAll());
assertEquals(200, response.getStatus());
+ // PUT with namespace, document type and group is also a restricted visit which requires a cluster.
+ access.expect(parameters -> {
+ fail("Not supposed to run");
+ });
+ response = driver.sendRequest("http://localhost/document/v1/space/music/group/troupe?selection=false", PUT);
+ assertSameJson("{" +
+ " \"pathId\": \"/document/v1/space/music/group/troupe\"," +
+ " \"message\": \"Must specify 'cluster' at '/document/v1/space/music/group/troupe'\"" +
+ "}", response.readAll());
+ assertEquals(400, response.getStatus());
+
// PUT with namespace, document type and group is also a restricted visit which requires a selection.
access.expect(parameters -> {
fail("Not supposed to run");
});
- response = driver.sendRequest("http://localhost/document/v1/space/music/group/troupe", PUT);
+ response = driver.sendRequest("http://localhost/document/v1/space/music/group/troupe?cluster=content", PUT);
assertSameJson("{" +
" \"pathId\": \"/document/v1/space/music/group/troupe\"," +
- " \"message\": \"Missing required property 'selection'\"" +
+ " \"message\": \"Must specify 'selection' at '/document/v1/space/music/group/troupe'\"" +
"}", response.readAll());
assertEquals(400, response.getStatus());
@@ -319,32 +333,44 @@ public class DocumentV1ApiTest {
access.expect(parameters -> {
assertEquals("(false) and (music) and (id.namespace=='space')", parameters.getDocumentSelection());
assertEquals("[id]", parameters.fieldSet());
+ assertEquals(60_000, parameters.getSessionTimeoutMs());
parameters.getLocalDataHandler().onMessage(new PutDocumentMessage(new DocumentPut(doc2)), tokens.get(0));
parameters.getControlHandler().onDone(VisitorControlHandler.CompletionCode.ABORTED, "Huzzah?");
});
access.session.expect((remove, parameters) -> {
DocumentRemove expectedRemove = new DocumentRemove(doc2.getId());
- expectedRemove.setCondition(new TestAndSetCondition("(false) and (music) and (id.namespace=='space')"));
- assertEquals(new DocumentRemove(doc2.getId()), remove);
- assertEquals(parameters().withRoute("zero"), parameters);
+ expectedRemove.setCondition(new TestAndSetCondition("false"));
+ assertEquals(expectedRemove, remove);
+ assertEquals(parameters().withRoute("content"), parameters);
parameters.responseHandler().get().handleResponse(new DocumentIdResponse(0, doc2.getId(), "boom", Response.Outcome.ERROR));
return new Result(Result.ResultType.SUCCESS, null);
});
- response = driver.sendRequest("http://localhost/document/v1/space/music/docid?selection=false&route=zero", DELETE);
+ response = driver.sendRequest("http://localhost/document/v1/space/music/docid?selection=false&cluster=content", DELETE);
assertSameJson("{" +
" \"pathId\": \"/document/v1/space/music/docid\"," +
" \"message\": \"boom\"" +
"}", response.readAll());
assertEquals(500, response.getStatus());
- // DELETE at the root is also a deletion visit. These require a selection.
+ // DELETE at the root is also a deletion visit. These also require a selection.
access.expect(parameters -> {
fail("Not supposed to run");
});
- response = driver.sendRequest("http://localhost/document/v1/space/music/docid", DELETE);
+ response = driver.sendRequest("http://localhost/document/v1/", DELETE);
assertSameJson("{" +
- " \"pathId\": \"/document/v1/space/music/docid\"," +
- " \"message\": \"Missing required property 'selection'\"" +
+ " \"pathId\": \"/document/v1/\"," +
+ " \"message\": \"Must specify 'selection' at '/document/v1/'\"" +
+ "}", response.readAll());
+ assertEquals(400, response.getStatus());
+
+ // DELETE at the root is also a deletion visit. These also require a cluster.
+ access.expect(parameters -> {
+ fail("Not supposed to run");
+ });
+ response = driver.sendRequest("http://localhost/document/v1/?selection=true", DELETE);
+ assertSameJson("{" +
+ " \"pathId\": \"/document/v1/\"," +
+ " \"message\": \"Must specify 'cluster' at '/document/v1/'\"" +
"}", response.readAll());
assertEquals(400, response.getStatus());
@@ -376,7 +402,7 @@ public class DocumentV1ApiTest {
// GET with full document ID is a document get operation which returns 404 when no document is found
access.session.expect((id, parameters) -> {
assertEquals(doc1.getId(), id);
- assertEquals(parameters().withRoute("[Content:cluster=content]").withFieldSet("go"), parameters);
+ assertEquals(parameters().withRoute("content").withFieldSet("go"), parameters);
parameters.responseHandler().get().handleResponse(new DocumentResponse(0, null));
return new Result(Result.ResultType.SUCCESS, null);
});
@@ -520,7 +546,7 @@ public class DocumentV1ApiTest {
access.session.expect((remove, parameters) -> {
DocumentRemove expectedRemove = new DocumentRemove(doc2.getId());
expectedRemove.setCondition(new TestAndSetCondition("false"));
- assertEquals(new DocumentRemove(doc2.getId()), remove);
+ assertEquals(expectedRemove, remove);
assertEquals(parameters().withRoute("route"), parameters);
parameters.responseHandler().get().handleResponse(new DocumentIdResponse(0, doc2.getId()));
return new Result(Result.ResultType.SUCCESS, null);