summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArnstein Ressem <aressem@gmail.com>2021-03-12 13:58:06 +0100
committerGitHub <noreply@github.com>2021-03-12 13:58:06 +0100
commit0394a2a4430ee34ea7cafe03c98592199eda492f (patch)
tree65acd8c44c09f0e00c03e85751fff733d95bccb9
parent5fc288ac15612f400879f7278d588541fe8257a4 (diff)
Revert "GC unused DiskState"
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/ClusterControllerStateRestAPI.java12
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Id.java12
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Response.java5
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/UnitPathResolver.java14
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/requests/PartitionStateRequest.java69
-rw-r--r--clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ClusterStateGeneratorTest.java1
-rw-r--r--clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/GroupAutoTakedownTest.java1
-rw-r--r--clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/NotMasterTest.java14
-rw-r--r--clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/PartitionTest.java49
-rw-r--r--clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/SetNodeStateTest.java10
-rw-r--r--vdslib/src/main/java/com/yahoo/vdslib/state/DiskState.java135
-rw-r--r--vdslib/src/test/java/com/yahoo/vdslib/state/DiskStateTestCase.java116
12 files changed, 431 insertions, 7 deletions
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/ClusterControllerStateRestAPI.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/ClusterControllerStateRestAPI.java
index 6c80973c4af..169684aa742 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/ClusterControllerStateRestAPI.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/ClusterControllerStateRestAPI.java
@@ -2,12 +2,7 @@
package com.yahoo.vespa.clustercontroller.core.restapiv2;
import com.yahoo.vespa.clustercontroller.core.RemoteClusterControllerTaskScheduler;
-import com.yahoo.vespa.clustercontroller.core.restapiv2.requests.ClusterListRequest;
-import com.yahoo.vespa.clustercontroller.core.restapiv2.requests.ClusterStateRequest;
-import com.yahoo.vespa.clustercontroller.core.restapiv2.requests.NodeStateRequest;
-import com.yahoo.vespa.clustercontroller.core.restapiv2.requests.ServiceStateRequest;
-import com.yahoo.vespa.clustercontroller.core.restapiv2.requests.SetNodeStateRequest;
-import com.yahoo.vespa.clustercontroller.core.restapiv2.requests.SetNodeStatesForClusterRequest;
+import com.yahoo.vespa.clustercontroller.core.restapiv2.requests.*;
import com.yahoo.vespa.clustercontroller.utils.staterestapi.StateRestAPI;
import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.InternalFailure;
import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.OtherMasterException;
@@ -17,6 +12,7 @@ import com.yahoo.vespa.clustercontroller.utils.staterestapi.requests.UnitStateRe
import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.SetResponse;
import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.UnitResponse;
+import java.util.EnumSet;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Logger;
@@ -90,6 +86,10 @@ public class ClusterControllerStateRestAPI implements StateRestAPI {
public Request<? extends UnitResponse> visitNode(Id.Node id) {
return new NodeStateRequest(id);
}
+ @Override
+ public Request<? extends UnitResponse> visitPartition(Id.Partition id) {
+ return new PartitionStateRequest(id, EnumSet.of(VerboseReport.STATISTICS));
+ }
});
if (req instanceof ClusterListRequest) {
log.fine("Got cluster list request");
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Id.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Id.java
index 1a0fe6cdf8e..ed5af93d7fb 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Id.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Id.java
@@ -52,4 +52,16 @@ public class Id {
public String toString() { return super.toString() + "/" + id; }
}
+ public static class Partition extends Node {
+ private final int id;
+
+ public Partition(Node n, int partition) {
+ super(n, n.id);
+ this.id = partition;
+ }
+
+ public final int getPartitionIndex() { return id; }
+ public String toString() { return super.toString() + "/" + id; }
+ }
+
}
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Response.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Response.java
index 82512262b4a..17949b82365 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Response.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Response.java
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.clustercontroller.core.restapiv2;
+import com.yahoo.vdslib.state.DiskState;
import com.yahoo.vdslib.state.NodeState;
import com.yahoo.vdslib.state.State;
import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.InternalFailure;
@@ -31,6 +32,10 @@ public class Response {
this.id = parseId(ns.getState());
this.reason = ns.getDescription();
}
+ public UnitStateImpl(DiskState ds) throws StateRestApiException {
+ this.id = parseId(ds.getState());
+ this.reason = ds.getDescription();
+ }
public String parseId(State id) throws StateRestApiException {
switch (id) {
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/UnitPathResolver.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/UnitPathResolver.java
index 04fd154ff6b..4dc73f4d0dd 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/UnitPathResolver.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/UnitPathResolver.java
@@ -6,6 +6,7 @@ import com.yahoo.vespa.clustercontroller.core.RemoteClusterControllerTaskSchedul
import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.MissingUnitException;
import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.OperationNotSupportedForUnitException;
import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.StateRestApiException;
+import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.UnitResponse;
import java.util.HashMap;
import java.util.Map;
@@ -18,6 +19,7 @@ public class UnitPathResolver<T> {
Request<? extends T> visitCluster(Id.Cluster id) throws StateRestApiException;
Request<? extends T> visitService(Id.Service id) throws StateRestApiException;
Request<? extends T> visitNode(Id.Node id) throws StateRestApiException;
+ Request<? extends T> visitPartition(Id.Partition id) throws StateRestApiException;
}
@@ -38,6 +40,7 @@ public class UnitPathResolver<T> {
public Request<? extends T> visitCluster(Id.Cluster id) throws StateRestApiException { return fail(); }
public Request<? extends T> visitService(Id.Service id) throws StateRestApiException { return fail(); }
public Request<? extends T> visitNode(Id.Node id) throws StateRestApiException { return fail(); }
+ public Request<? extends T> visitPartition(Id.Partition id) throws StateRestApiException { return fail(); }
}
@@ -84,7 +87,16 @@ public class UnitPathResolver<T> {
if (path.length == 3) {
return visitor.visitNode(node);
}
- throw new MissingUnitException(path, 3);
+ Id.Partition partition;
+ try{
+ partition = new Id.Partition(node, Integer.valueOf(path[3]));
+ } catch (NumberFormatException e) {
+ throw new MissingUnitException(path, 3);
+ }
+ if (path.length == 4) {
+ return visitor.visitPartition(partition);
+ }
+ throw new MissingUnitException(path, 4);
}
}
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/requests/PartitionStateRequest.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/requests/PartitionStateRequest.java
new file mode 100644
index 00000000000..bb42af45ef3
--- /dev/null
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/requests/PartitionStateRequest.java
@@ -0,0 +1,69 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.clustercontroller.core.restapiv2.requests;
+
+import com.yahoo.vdslib.state.DiskState;
+import com.yahoo.vdslib.state.NodeState;
+import com.yahoo.vespa.clustercontroller.core.RemoteClusterControllerTask;
+import com.yahoo.vespa.clustercontroller.core.hostinfo.Metrics;
+import com.yahoo.vespa.clustercontroller.core.restapiv2.Id;
+import com.yahoo.vespa.clustercontroller.core.restapiv2.Request;
+import com.yahoo.vespa.clustercontroller.core.restapiv2.Response;
+import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.StateRestApiException;
+
+import java.util.Set;
+import java.util.logging.Logger;
+
+public class PartitionStateRequest extends Request<Response.PartitionResponse> {
+ private static final Logger log = Logger.getLogger(PartitionStateRequest.class.getName());
+ private final Id.Partition id;
+ private final Set<VerboseReport> verboseReports;
+
+ public PartitionStateRequest(Id.Partition id, Set<VerboseReport> verboseReports) {
+ super(MasterState.MUST_BE_MASTER);
+ this.id = id;
+ this.verboseReports = verboseReports;
+ }
+
+ @Override
+ public Response.PartitionResponse calculateResult(RemoteClusterControllerTask.Context context)
+ throws StateRestApiException {
+ Response.PartitionResponse result = new Response.PartitionResponse();
+ if (verboseReports.contains(VerboseReport.STATISTICS)) {
+ fillInMetrics(context.cluster.getNodeInfo(id.getNode()).getHostInfo().getMetrics(), result);
+ }
+ NodeState nodeState = context.currentConsolidatedState.getNodeState(id.getNode());
+ DiskState diskState = new DiskState();
+ result.addState("generated", new Response.UnitStateImpl(diskState));
+
+ return result;
+ }
+
+ private static void fillInMetrics(Metrics metrics, Response.PartitionResponse result) {
+ for (Metrics.Metric metric: metrics.getMetrics()) {
+ fillInMetricValue(metric.getName(), metric.getValue(), result);
+ }
+ }
+
+ private static void fillInMetricValue(
+ String name, Metrics.Value value, Response.PartitionResponse result) {
+ if (name.equals("vds.datastored.alldisks.docs")) {
+ if (value.getLast() == null) {
+ log.warning("Proper doc count value did not exist in value set.");
+ return;
+ }
+ result.addMetric("unique-document-count", value.getLast());
+ } else if (name.equals("vds.datastored.alldisks.bytes")) {
+ if (value.getLast() == null) {
+ log.warning("Proper doc size value did not exist in value set.");
+ return;
+ }
+ result.addMetric("unique-document-total-size", value.getLast());
+ } else if (name.equals("vds.datastored.alldisks.buckets")) {
+ if (value.getLast() == null) {
+ log.warning("Proper bucket count value did not exist in value set.");
+ return;
+ }
+ result.addMetric("bucket-count", value.getLast());
+ }
+ }
+}
diff --git a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ClusterStateGeneratorTest.java b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ClusterStateGeneratorTest.java
index 0798e6174f9..bf191768892 100644
--- a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ClusterStateGeneratorTest.java
+++ b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ClusterStateGeneratorTest.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.clustercontroller.core;
import com.yahoo.vdslib.distribution.ConfiguredNode;
+import com.yahoo.vdslib.state.DiskState;
import com.yahoo.vdslib.state.Node;
import com.yahoo.vdslib.state.NodeState;
import com.yahoo.vdslib.state.NodeType;
diff --git a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/GroupAutoTakedownTest.java b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/GroupAutoTakedownTest.java
index d33d235bc66..bd77f42ff8a 100644
--- a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/GroupAutoTakedownTest.java
+++ b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/GroupAutoTakedownTest.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.clustercontroller.core;
import com.yahoo.vdslib.distribution.ConfiguredNode;
+import com.yahoo.vdslib.state.DiskState;
import com.yahoo.vdslib.state.Node;
import com.yahoo.vdslib.state.NodeState;
import com.yahoo.vdslib.state.NodeType;
diff --git a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/NotMasterTest.java b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/NotMasterTest.java
index b0355844341..4ec5893c1e7 100644
--- a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/NotMasterTest.java
+++ b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/NotMasterTest.java
@@ -54,6 +54,12 @@ public class NotMasterTest extends StateRestApiTest {
assertTrue(e.getMessage(), e.getMessage().contains("No known master cluster controller"));
}
try{
+ restAPI.getState(new StateRequest("music/storage/1/0", 0));
+ fail();
+ } catch (UnknownMasterException e) {
+ assertTrue(e.getMessage(), e.getMessage().contains("No known master cluster controller"));
+ }
+ try{
restAPI.setUnitState(new SetNodeStateTest.SetUnitStateRequestImpl("music/storage/1")
.setNewState("user", "down", "test"));
fail();
@@ -112,6 +118,14 @@ public class NotMasterTest extends StateRestApiTest {
assertEquals(e.getPort(), getDefaults().vespaWebServicePort());
}
try{
+ restAPI.getState(new StateRequest("music/storage/1/0", 0));
+ fail();
+ } catch (OtherMasterException e) {
+ assertTrue(e.getMessage(), e.getMessage().contains("Cluster controller not master. Use master at otherhost:" + getDefaults().vespaWebServicePort() + "."));
+ assertEquals("otherhost", e.getHost());
+ assertEquals(e.getPort(), getDefaults().vespaWebServicePort());
+ }
+ try{
restAPI.setUnitState(new SetNodeStateTest.SetUnitStateRequestImpl("music/storage/1")
.setNewState("user", "down", "test"));
fail();
diff --git a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/PartitionTest.java b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/PartitionTest.java
new file mode 100644
index 00000000000..106803ec3f1
--- /dev/null
+++ b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/PartitionTest.java
@@ -0,0 +1,49 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.clustercontroller.core.restapiv2;
+
+import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.UnitResponse;
+import org.codehaus.jettison.json.JSONObject;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class PartitionTest extends StateRestApiTest {
+
+ @Test
+ public void testPartition() throws Exception {
+ setUp(true);
+ UnitResponse response = restAPI.getState(new StateRequest("music/storage/1/0", 0));
+ String expected =
+ "{\n" +
+ " \"state\": {\"generated\": {\n" +
+ " \"state\": \"up\",\n" +
+ " \"reason\": \"\"\n" +
+ " }},\n" +
+ " \"metrics\": {\n" +
+ " \"bucket-count\": 1,\n" +
+ " \"unique-document-count\": 2,\n" +
+ " \"unique-document-total-size\": 3\n" +
+ " }\n" +
+ "}";
+ assertEquals(expected, jsonWriter.createJson(response).toString(2));
+ }
+
+ @Test
+ public void testRecursiveCluster() throws Exception {
+ setUp(true);
+ UnitResponse response = restAPI.getState(new StateRequest("music/storage/1/0", 1));
+ String expected =
+ "{\n" +
+ " \"state\": {\"generated\": {\n" +
+ " \"state\": \"up\",\n" +
+ " \"reason\": \"\"\n" +
+ " }},\n" +
+ " \"metrics\": {\n" +
+ " \"bucket-count\": 1,\n" +
+ " \"unique-document-count\": 2,\n" +
+ " \"unique-document-total-size\": 3\n" +
+ " }\n" +
+ "}";
+ assertEquals(expected, jsonWriter.createJson(response).toString(2));
+ }
+}
diff --git a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/SetNodeStateTest.java b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/SetNodeStateTest.java
index 0e9a4808e9b..03fdb15971f 100644
--- a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/SetNodeStateTest.java
+++ b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/SetNodeStateTest.java
@@ -366,6 +366,16 @@ public class SetNodeStateTest extends StateRestApiTest {
} catch (OperationNotSupportedForUnitException e) {
assertTrue(e.getMessage(), e.getMessage().contains(wrongUnitMessage));
}
+
+ // ... setting at node-level is allowed
+
+ try{
+ restAPI.setUnitState(new SetUnitStateRequestImpl(
+ "music/storage/1/0").setNewState("user", "down", "testing"));
+ fail();
+ } catch (OperationNotSupportedForUnitException e) {
+ assertTrue(e.getMessage(), e.getMessage().contains(wrongUnitMessage));
+ }
}
@Test
diff --git a/vdslib/src/main/java/com/yahoo/vdslib/state/DiskState.java b/vdslib/src/main/java/com/yahoo/vdslib/state/DiskState.java
new file mode 100644
index 00000000000..1a05f8ab565
--- /dev/null
+++ b/vdslib/src/main/java/com/yahoo/vdslib/state/DiskState.java
@@ -0,0 +1,135 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vdslib.state;
+
+import com.yahoo.text.StringUtilities;
+
+import java.util.StringTokenizer;
+import java.text.ParseException;
+
+/**
+ *
+ */
+public class DiskState implements Cloneable {
+ private State state = State.UP;
+ private String description = "";
+ private double capacity = 1.0;
+
+ public DiskState() {}
+ public DiskState(State s) {
+ setState(s);
+ }
+ public DiskState(State s, String description, double capacity) {
+ setState(s); // Set via set methods, so we can have illegal argument checks only one place
+ setCapacity(capacity);
+ setDescription(description);
+ }
+ public DiskState clone() {
+ try{
+ return (DiskState) super.clone();
+ } catch (CloneNotSupportedException e) {
+ assert(false); // Should not happen
+ return null;
+ }
+ }
+
+ public DiskState(String serialized) throws ParseException {
+ StringTokenizer st = new StringTokenizer(serialized, " \t\f\r\n");
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ int index = token.indexOf(':');
+ if (index < 0) {
+ throw new ParseException("Token " + token + " does not contain ':': " + serialized, 0);
+ }
+ String key = token.substring(0, index);
+ String value = token.substring(index + 1);
+ if (key.length() > 0) switch (key.charAt(0)) {
+ case 's':
+ if (key.length() > 1) break;
+ setState(State.get(value));
+ continue;
+ case 'c':
+ if (key.length() > 1) break;
+ try{
+ setCapacity(Double.valueOf(value));
+ } catch (Exception e) {
+ throw new ParseException("Illegal disk capacity '" + value + "'. Capacity must be a positive floating point number", 0);
+ }
+ continue;
+ case 'm':
+ if (key.length() > 1) break;
+ description = StringUtilities.unescape(value);
+ continue;
+ default:
+ break;
+ }
+ // Ignore unknown tokens
+ }
+ }
+
+ public String serialize(String prefix, boolean includeDescription) {
+ boolean empty = true;
+ StringBuilder sb = new StringBuilder();
+ if (!state.equals(State.UP) || prefix.length() < 2) {
+ sb.append(prefix).append("s:").append(state.serialize());
+ empty = false;
+ }
+ if (Math.abs(capacity - 1.0) > 0.000000001) {
+ if (empty) { empty = false; } else { sb.append(' '); }
+ sb.append(prefix).append("c:").append(capacity);
+ }
+ if (includeDescription && description.length() > 0) {
+ if (!empty) { sb.append(' '); }
+ sb.append(prefix).append("m:").append(StringUtilities.escape(description, ' '));
+ }
+ return sb.toString();
+ }
+
+ public State getState() { return state; }
+ public double getCapacity() { return capacity; }
+ public String getDescription() { return description; }
+
+ public void setState(State s) {
+ if (!s.validDiskState()) {
+ throw new IllegalArgumentException("State " + s + " is not a valid disk state.");
+ }
+ state = s;
+ }
+ public void setCapacity(double capacity) {
+ if (capacity < 0) {
+ throw new IllegalArgumentException("Negative capacity makes no sense.");
+ }
+ this.capacity = capacity;
+ }
+ public void setDescription(String desc) { description = desc; }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DiskState(").append(state.serialize());
+ if (Math.abs(capacity - 1.0) > 0.00000001) {
+ sb.append(", capacity ").append(capacity);
+ }
+ if (description.length() > 0) {
+ sb.append(": ").append(description);
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof DiskState)) { return false; }
+ DiskState other = (DiskState) o;
+ if (state.equals(other.state)
+ && Math.abs(capacity - other.capacity) < 0.00000001)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ // NOTE: capacity cannot be part of the hashCode
+ return state.hashCode();
+ }
+}
diff --git a/vdslib/src/test/java/com/yahoo/vdslib/state/DiskStateTestCase.java b/vdslib/src/test/java/com/yahoo/vdslib/state/DiskStateTestCase.java
new file mode 100644
index 00000000000..bdc3be3dc70
--- /dev/null
+++ b/vdslib/src/test/java/com/yahoo/vdslib/state/DiskStateTestCase.java
@@ -0,0 +1,116 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vdslib.state;
+
+import org.junit.Test;
+
+import java.text.ParseException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class DiskStateTestCase {
+
+ private static final double delta = 0.0000000001;
+
+ @Test
+ public void testEquals() {
+ DiskState d1 = new DiskState(State.UP, "", 1);
+ DiskState d2 = new DiskState(State.UP, "", 2);
+ DiskState d3 = new DiskState(State.DOWN, "Failed disk", 1);
+ DiskState d4 = new DiskState(State.DOWN, "IO error", 1);
+ DiskState d5 = new DiskState(State.UP, "", 1);
+ DiskState d6 = new DiskState(State.UP, "", 2);
+ DiskState d7 = new DiskState(State.DOWN, "Failed disk", 1);
+ DiskState d8 = new DiskState(State.DOWN, "IO error", 1);
+
+ assertEquals(d1, d5);
+ assertEquals(d2, d6);
+ assertEquals(d3, d7);
+ assertEquals(d4, d8);
+
+ assertFalse(d1.equals(d2));
+ assertFalse(d1.equals(d3));
+ assertFalse(d1.equals(d4));
+
+ assertFalse(d2.equals(d1));
+ assertFalse(d2.equals(d3));
+ assertFalse(d2.equals(d4));
+
+ assertFalse(d3.equals(d1));
+ assertFalse(d3.equals(d2));
+ assertEquals(d3, d4);
+
+ assertFalse(d4.equals(d1));
+ assertFalse(d4.equals(d2));
+ assertEquals(d4, d3);
+
+ assertFalse(d1.equals("class not instance of Node"));
+ }
+
+ @Test
+ public void testSerialization() throws ParseException {
+ DiskState d = new DiskState();
+ DiskState other = new DiskState(d.serialize("", true));
+ assertEquals(d, other);
+ assertEquals(d.toString(), other.toString());
+ assertEquals(State.UP, other.getState());
+ assertEquals(1.0, other.getCapacity(), delta);
+ assertEquals("", other.getDescription());
+ assertEquals("s:u", d.serialize("", false));
+ assertEquals("s:u", d.serialize("", true));
+ assertEquals("", d.serialize(".0.", false));
+ assertEquals("", d.serialize(".0.", true));
+
+ assertEquals(d, new DiskState(": s:u sbadkey:somevalue cbadkey:somevalue mbadkey:somevalue unknwonkey:somevalue"));
+
+ d = new DiskState(State.UP, "Slow disk", 1.0);
+ other = new DiskState(d.serialize("", true));
+ assertEquals(d, other);
+ assertEquals(d.toString(), other.toString());
+ assertEquals(State.UP, other.getState());
+ assertEquals(1.0, other.getCapacity(), delta);
+ assertEquals("Slow disk", other.getDescription());
+ assertEquals("s:u", d.serialize("", false));
+ assertEquals("s:u m:Slow\\x20disk", d.serialize("", true));
+ assertEquals("", d.serialize(".0.", false));
+ assertEquals(".0.m:Slow\\x20disk", d.serialize(".0.", true));
+
+ d = new DiskState(State.DOWN, "Failed disk", 2.0);
+ other = new DiskState(d.serialize("", true));
+ assertEquals(d, other);
+ assertEquals(d.toString(), other.toString());
+ assertEquals(State.DOWN, other.getState());
+ assertEquals(2.0, other.getCapacity(), delta);
+ assertEquals("Failed disk", other.getDescription());
+ assertEquals("s:d c:2.0", d.serialize("", false));
+ assertEquals("s:d c:2.0 m:Failed\\x20disk", d.serialize("", true));
+ assertEquals(".0.s:d .0.c:2.0", d.serialize(".0.", false));
+ assertEquals(".0.s:d .0.c:2.0 .0.m:Failed\\x20disk", d.serialize(".0.", true));
+
+ try {
+ new DiskState(State.MAINTENANCE);
+ assertTrue("Method expected to throw IllegalArgumentException", false);
+ } catch (IllegalArgumentException e) {
+ assertEquals("State " + State.MAINTENANCE + " is not a valid disk state.", e.getMessage());
+ }
+ try {
+ new DiskState(State.UP, "", -1);
+ assertTrue("Method expected to throw IllegalArgumentException", false);
+ } catch (IllegalArgumentException e) {
+ assertEquals("Negative capacity makes no sense.", e.getMessage());
+ }
+ try {
+ new DiskState("nocolon");
+ assertTrue("Method expected to throw ParseException", false);
+ } catch (ParseException e) {
+ assertEquals("Token nocolon does not contain ':': nocolon", e.getMessage());
+ }
+ try {
+ new DiskState("s:d c:badvalue");
+ assertTrue("Method expected to throw ParseException", false);
+ } catch (ParseException e) {
+ assertEquals("Illegal disk capacity 'badvalue'. Capacity must be a positive floating point number", e.getMessage());
+ }
+ }
+}