diff options
Diffstat (limited to 'clustercontroller-utils')
12 files changed, 390 insertions, 380 deletions
diff --git a/clustercontroller-utils/pom.xml b/clustercontroller-utils/pom.xml index 0d8e30d3693..c534794ad0a 100644 --- a/clustercontroller-utils/pom.xml +++ b/clustercontroller-utils/pom.xml @@ -14,8 +14,8 @@ <packaging>container-plugin</packaging> <dependencies> <dependency> - <groupId>org.codehaus.jettison</groupId> - <artifactId>jettison</artifactId> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>com.yahoo.vespa</groupId> diff --git a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/communication/http/HttpResult.java b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/communication/http/HttpResult.java index ab259289d58..5f3bf21f40e 100644 --- a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/communication/http/HttpResult.java +++ b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/communication/http/HttpResult.java @@ -1,6 +1,8 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.clustercontroller.utils.communication.http; +import com.fasterxml.jackson.databind.JsonNode; + import java.util.Collection; import java.util.LinkedList; import java.util.List; @@ -83,7 +85,7 @@ public class HttpResult { return sb.toString(); } public void printContent(StringBuilder sb) { - sb.append(content.toString()); + sb.append(content instanceof JsonNode ? ((JsonNode) content).toPrettyString() : content.toString()); } } diff --git a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/communication/http/JsonHttpResult.java b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/communication/http/JsonHttpResult.java index 7d8877e974c..a6326461807 100644 --- a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/communication/http/JsonHttpResult.java +++ b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/communication/http/JsonHttpResult.java @@ -1,13 +1,16 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.clustercontroller.utils.communication.http; -import com.yahoo.vespa.clustercontroller.utils.util.JSONObjectWrapper; -import org.codehaus.jettison.json.JSONException; -import org.codehaus.jettison.json.JSONObject; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; public class JsonHttpResult extends HttpResult { - private JSONObject json; + private static final ObjectMapper mapper = new ObjectMapper(); + + private JsonNode json; private boolean failedParsing = false; @@ -19,56 +22,51 @@ public class JsonHttpResult extends HttpResult { super(other); if (other.getContent() == null) { - setParsedJson(new JSONObject()); + setParsedJson(new ObjectNode(mapper.getNodeFactory())); return; } try{ - if (other.getContent() instanceof JSONObject) { - setParsedJson((JSONObject) other.getContent()); + if (other.getContent() instanceof JsonNode jsonContent) { + setParsedJson(jsonContent); } else { - setParsedJson(new JSONObject(other.getContent().toString())); + setParsedJson(mapper.readTree(other.getContent().toString())); } - } catch (JSONException e) { + } + catch (JsonProcessingException e) { failedParsing = true; setParsedJson(createErrorJson(e.getMessage(), other)); } } - private JSONObject createErrorJson(String error, HttpResult other) { - return new JSONObjectWrapper() - .put("error", "Invalid JSON in output: " + error) - .put("output", other.getContent().toString()); + private JsonNode createErrorJson(String error, HttpResult other) { + ObjectNode root = new ObjectNode(mapper.getNodeFactory()); + root.put("error", "Invalid JSON in output: " + error); + root.put("output", other.getContent().toString()); + return root; } - public JsonHttpResult setJson(JSONObject o) { + public JsonHttpResult setJson(JsonNode o) { setContent(o); json = o; return this; } - private void setParsedJson(JSONObject o) { + private void setParsedJson(JsonNode o) { json = o; } - public JSONObject getJson() { + public JsonNode getJson() { return json; } @Override public void printContent(StringBuilder sb) { - if (failedParsing) { + if (failedParsing || json == null) { super.printContent(sb); - return; } - if (json != null) { + else { sb.append("JSON: "); - try{ - sb.append(json.toString(2)); - } catch (JSONException e) { - sb.append(json.toString()); - } - } else { - super.printContent(sb); + sb.append(json.toPrettyString()); } } diff --git a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/response/UnitMetrics.java b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/response/UnitMetrics.java index f0470a1b845..71e57fef6aa 100644 --- a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/response/UnitMetrics.java +++ b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/response/UnitMetrics.java @@ -5,6 +5,6 @@ import java.util.Map; public interface UnitMetrics { - Map<String, Number> getMetricMap(); + Map<String, Long> getMetricMap(); } diff --git a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonReader.java b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonReader.java index 44c45a2e8da..26ee121af79 100644 --- a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonReader.java +++ b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonReader.java @@ -1,18 +1,24 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.clustercontroller.utils.staterestapi.server; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.yahoo.vespa.clustercontroller.utils.communication.http.HttpRequest; import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.InvalidContentException; import com.yahoo.vespa.clustercontroller.utils.staterestapi.requests.SetUnitStateRequest; import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.UnitState; -import org.codehaus.jettison.json.JSONArray; -import org.codehaus.jettison.json.JSONObject; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; public class JsonReader { + + private static final ObjectMapper mapper = new ObjectMapper(); + private static class UnitStateImpl implements UnitState { + private final String id; private final String reason; @@ -30,6 +36,7 @@ public class JsonReader { public String getReason() { return reason; } + } static class SetRequestData { @@ -50,60 +57,56 @@ public class JsonReader { } public SetRequestData getStateRequestData(HttpRequest request) throws Exception { - JSONObject json = new JSONObject(request.getPostContent().toString()); + JsonNode json = mapper.readTree(request.getPostContent().toString()); - final boolean probe = json.has("probe") && json.getBoolean("probe"); + final boolean probe = json.has("probe") && json.get("probe").booleanValue(); final SetUnitStateRequest.Condition condition; if (json.has("condition")) { - condition = SetUnitStateRequest.Condition.fromString(json.getString("condition")); + condition = SetUnitStateRequest.Condition.fromString(json.get("condition").textValue()); } else { condition = SetUnitStateRequest.Condition.FORCE; } final SetUnitStateRequest.ResponseWait responseWait = json.has("response-wait") - ? SetUnitStateRequest.ResponseWait.fromString(json.getString("response-wait")) + ? SetUnitStateRequest.ResponseWait.fromString(json.get("response-wait").textValue()) : SetUnitStateRequest.ResponseWait.WAIT_UNTIL_CLUSTER_ACKED; Map<String, UnitState> stateMap = new HashMap<>(); if (!json.has("state")) { throw new InvalidContentException("Set state requests must contain a state object"); } - Object o = json.get("state"); - if (!(o instanceof JSONObject)) { + JsonNode o = json.get("state"); + if ( ! (o instanceof ObjectNode state)) { throw new InvalidContentException("value of state is not a json object"); } - JSONObject state = (JSONObject) o; - - JSONArray stateTypes = state.names(); - for (int i=0; i<stateTypes.length(); ++i) { - o = stateTypes.get(i); - String type = (String) o; - o = state.get(type); - if (!(o instanceof JSONObject)) { + for (Iterator<Map.Entry<String, JsonNode>> fields = state.fields(); fields.hasNext(); ) { + Map.Entry<String, JsonNode> entry = fields.next(); + String type = entry.getKey(); + if ( ! (entry.getValue() instanceof ObjectNode userState)) { throw new InvalidContentException("value of state->" + type + " is not a json object"); } - JSONObject userState = (JSONObject) o; String code = "up"; if (userState.has("state")) { o = userState.get("state"); - if (!(o instanceof String)) { + if ( ! o.isTextual()) { throw new InvalidContentException("value of state->" + type + "->state is not a string"); } - code = o.toString(); + code = o.textValue(); } String reason = ""; if (userState.has("reason")) { o = userState.get("reason"); - if (!(o instanceof String)) { + if ( ! o.isTextual()) { throw new InvalidContentException("value of state->" + type + "->reason is not a string"); } - reason = o.toString(); + reason = o.textValue(); } stateMap.put(type, new UnitStateImpl(code, reason)); } return new SetRequestData(probe, stateMap, condition, responseWait); } + } diff --git a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonWriter.java b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonWriter.java index a6cde601ef4..af776ddf5cd 100644 --- a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonWriter.java +++ b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonWriter.java @@ -1,19 +1,29 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.clustercontroller.utils.staterestapi.server; -import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.*; -import org.codehaus.jettison.json.JSONArray; -import org.codehaus.jettison.json.JSONException; -import org.codehaus.jettison.json.JSONObject; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.CurrentUnitState; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.DistributionState; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.DistributionStates; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.SetResponse; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.SubUnitList; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.UnitAttributes; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.UnitMetrics; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.UnitResponse; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.response.UnitState; import java.util.Map; public class JsonWriter { + private static final ObjectMapper mapper = new ObjectMapper(); + private String pathPrefix = "/"; - public JsonWriter() { - } + public JsonWriter() { } public void setDefaultPathPrefix(String defaultPathPrefix) { if (defaultPathPrefix.isEmpty() || defaultPathPrefix.charAt(0) != '/') { @@ -22,16 +32,16 @@ public class JsonWriter { this.pathPrefix = defaultPathPrefix; } - public JSONObject createJson(UnitResponse data) throws Exception { - JSONObject json = new JSONObject(); + public JsonNode createJson(UnitResponse data) { + ObjectNode json = new ObjectNode(mapper.getNodeFactory()); fillInJson(data, json); return json; } - public void fillInJson(UnitResponse data, JSONObject json) throws Exception { + public void fillInJson(UnitResponse data, ObjectNode json) { UnitAttributes attributes = data.getAttributes(); if (attributes != null) { - fillInJson(attributes, json); + json.putPOJO("attributes", attributes.getAttributeValues()); } CurrentUnitState stateData = data.getCurrentState(); if (stateData != null) { @@ -39,7 +49,7 @@ public class JsonWriter { } UnitMetrics metrics = data.getMetrics(); if (metrics != null) { - fillInJson(metrics, json); + json.putPOJO("metrics", metrics.getMetricMap()); } Map<String, SubUnitList> subUnits = data.getSubUnits(); if (subUnits != null) { @@ -51,88 +61,45 @@ public class JsonWriter { } } - public void fillInJson(CurrentUnitState stateData, JSONObject json) throws Exception { - JSONObject stateJson = new JSONObject(); - json.put("state", stateJson); + public void fillInJson(CurrentUnitState stateData, ObjectNode json) { + ObjectNode stateJson = json.putObject("state"); Map<String, UnitState> state = stateData.getStatePerType(); - for (Map.Entry<String, UnitState> e : state.entrySet()) { - String stateType = e.getKey(); - UnitState unitState = e.getValue(); - JSONObject stateTypeJson = new JSONObject() - .put("state", unitState.getId()) - .put("reason", unitState.getReason()); - stateJson.put(stateType, stateTypeJson); - } + state.forEach((stateType, unitState) -> stateJson.putObject(stateType) + .put("state", unitState.getId()) + .put("reason", unitState.getReason())); } - public void fillInJson(UnitMetrics metrics, JSONObject json) throws Exception { - JSONObject metricsJson = new JSONObject(); - for (Map.Entry<String, Number> e : metrics.getMetricMap().entrySet()) { - metricsJson.put(e.getKey(), e.getValue()); - } - json.put("metrics", metricsJson); - } - public void fillInJson(UnitAttributes attributes, JSONObject json) throws Exception { - JSONObject attributesJson = new JSONObject(); - for (Map.Entry<String, String> e : attributes.getAttributeValues().entrySet()) { - attributesJson.put(e.getKey(), e.getValue()); - } - json.put("attributes", attributesJson); + public void fillInJson(Map<String, SubUnitList> subUnitMap, ObjectNode json) { + subUnitMap.forEach((subUnitType, units) -> { + ObjectNode typeJson = json.putObject(subUnitType); + units.getSubUnitLinks().forEach((key, value) -> typeJson.putObject(key).put("link", pathPrefix + "/" + value)); + units.getSubUnits().forEach((key, value) -> fillInJson(value, typeJson.putObject(key))); + }); } - public void fillInJson(Map<String, SubUnitList> subUnitMap, JSONObject json) throws Exception { - for(Map.Entry<String, SubUnitList> e : subUnitMap.entrySet()) { - String subUnitType = e.getKey(); - JSONObject typeJson = new JSONObject(); - for (Map.Entry<String, String> f : e.getValue().getSubUnitLinks().entrySet()) { - JSONObject linkJson = new JSONObject(); - linkJson.put("link", pathPrefix + "/" + f.getValue()); - typeJson.put(f.getKey(), linkJson); - } - for (Map.Entry<String, UnitResponse> f : e.getValue().getSubUnits().entrySet()) { - JSONObject subJson = new JSONObject(); - fillInJson(f.getValue(), subJson); - typeJson.put(f.getKey(), subJson); - } - json.put(subUnitType, typeJson); - } - } - - private static void fillInJson(DistributionStates states, JSONObject json) throws Exception { - JSONObject statesJson = new JSONObject(); - statesJson.put("published", distributionStateToJson(states.getPublishedState())); - json.put("distribution-states", statesJson); + private static void fillInJson(DistributionStates states, ObjectNode json) { + fillDistributionState(states.getPublishedState(), + json.putObject("distribution-states") + .putObject("published")); } - private static JSONObject distributionStateToJson(DistributionState state) throws Exception { - JSONObject result = new JSONObject(); + private static void fillDistributionState(DistributionState state, ObjectNode result) { result.put("baseline", state.getBaselineState()); - JSONArray bucketSpacesJson = new JSONArray(); - for (Map.Entry<String, String> entry : state.getBucketSpaceStates().entrySet()) { - JSONObject bucketSpaceJson = new JSONObject(); - bucketSpaceJson.put("name", entry.getKey()); - bucketSpaceJson.put("state", entry.getValue()); - bucketSpacesJson.put(bucketSpaceJson); - } - result.put("bucket-spaces", bucketSpacesJson); - return result; + ArrayNode bucketSpacesJson = result.putArray("bucket-spaces"); + state.getBucketSpaceStates().forEach((key, value) -> { + ObjectNode bucketSpaceJson = bucketSpacesJson.addObject(); + bucketSpaceJson.put("name", key); + bucketSpaceJson.put("state", value); + }); } - public JSONObject createErrorJson(String description) { - JSONObject o = new JSONObject(); - try{ - o.put("message", description); - } catch (JSONException e) { - // Can't really do anything if we get an error trying to report an error. - } - return o; + public JsonNode createErrorJson(String description) { + return new ObjectNode(mapper.getNodeFactory()).put("message", description); } - public JSONObject createJson(SetResponse setResponse) throws JSONException { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("wasModified", setResponse.getWasModified()); - jsonObject.put("reason", setResponse.getReason()); - return jsonObject; + public JsonNode createJson(SetResponse setResponse) { + return new ObjectNode(mapper.getNodeFactory()).put("wasModified", setResponse.getWasModified()) + .put("reason", setResponse.getReason()); } } diff --git a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/util/JSONObjectWrapper.java b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/util/JSONObjectWrapper.java deleted file mode 100644 index cafc208ba79..00000000000 --- a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/util/JSONObjectWrapper.java +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.clustercontroller.utils.util; - -import org.codehaus.jettison.json.JSONException; -import org.codehaus.jettison.json.JSONObject; - -/** - * The Jettison json object class has an interface issue where it hides null pointer exceptions - * as checked json exceptions. Consequently one has to create catch clauses that code cannot get - * into. This class hides those exceptions. - * - * (Add functions to this wrapper for new functions needing to hide exceptions like this as they are - * needed) - */ -public class JSONObjectWrapper extends JSONObject { - - @Override - public JSONObjectWrapper put(String key, Object value) { - try{ - super.put(key, value); - return this; - } catch (JSONException e) { - throw new NullPointerException(e.getMessage()); - } - } - -} diff --git a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/communication/http/JsonHttpResultTest.java b/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/communication/http/JsonHttpResultTest.java index 19fc27669af..076851cbf5e 100644 --- a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/communication/http/JsonHttpResultTest.java +++ b/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/communication/http/JsonHttpResultTest.java @@ -1,8 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.clustercontroller.utils.communication.http; -import org.codehaus.jettison.json.JSONException; -import org.codehaus.jettison.json.JSONObject; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -16,14 +14,18 @@ public class JsonHttpResultTest { @Test void testOutput() { - assertEquals("HTTP 200/OK\n" - + "\n" - + "JSON: {\"foo\": 3}", - new JsonHttpResult(new HttpResult().setContent("{ \"foo\" : 3 }")).toString(true)); - assertEquals("HTTP 200/OK\n" - + "\n" - + "{ \"foo\" : }", - new JsonHttpResult(new HttpResult().setContent("{ \"foo\" : }")).toString(true)); + assertEquals(""" + HTTP 200/OK + + JSON: { + "foo" : 3 + }""", + new JsonHttpResult(new HttpResult().setContent("{ \"foo\" : 3 }")).toString(true)); + assertEquals(""" + HTTP 200/OK + + { "foo" : }""", + new JsonHttpResult(new HttpResult().setContent("{ \"foo\" : }")).toString(true)); } @Test @@ -35,18 +37,4 @@ public class JsonHttpResultTest { assertEquals("Foo", sb.toString()); } - @Test - void testInvalidJsonOutput() { - JsonHttpResult result = new JsonHttpResult(); - result.setJson(new JSONObject() { - @Override - public String toString(int indent) throws JSONException { - throw new JSONException("Foo"); - } - }); - StringBuilder sb = new StringBuilder(); - result.printContent(sb); - assertEquals("JSON: {}", sb.toString()); - } - } diff --git a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/DummyStateApi.java b/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/DummyStateApi.java index dd55d023284..247aec369cb 100644 --- a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/DummyStateApi.java +++ b/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/DummyStateApi.java @@ -136,9 +136,9 @@ public class DummyStateApi implements StateRestAPI { public UnitMetrics getMetrics() { return new UnitMetrics() { @Override - public Map<String, Number> getMetricMap() { - Map<String, Number> m = new LinkedHashMap<>(); - m.put("doc-count", node.docCount); + public Map<String, Long> getMetricMap() { + Map<String, Long> m = new LinkedHashMap<>(); + m.put("doc-count", (long) node.docCount); return m; } }; diff --git a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/StateRestAPITest.java b/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/StateRestAPITest.java index d0378dd9065..47b12f883ff 100644 --- a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/StateRestAPITest.java +++ b/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/StateRestAPITest.java @@ -1,22 +1,35 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.clustercontroller.utils.staterestapi; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.yahoo.vespa.clustercontroller.utils.communication.async.AsyncOperation; import com.yahoo.vespa.clustercontroller.utils.communication.async.AsyncUtils; import com.yahoo.vespa.clustercontroller.utils.communication.http.HttpRequest; import com.yahoo.vespa.clustercontroller.utils.communication.http.HttpResult; -import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.*; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.DeadlineExceededException; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.InternalFailure; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.InvalidContentException; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.InvalidOptionValueException; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.OperationNotSupportedForUnitException; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.OtherMasterException; +import com.yahoo.vespa.clustercontroller.utils.staterestapi.errors.UnknownMasterException; import com.yahoo.vespa.clustercontroller.utils.staterestapi.server.RestApiHandler; import com.yahoo.vespa.clustercontroller.utils.test.TestTransport; -import org.codehaus.jettison.json.JSONObject; import org.junit.jupiter.api.Test; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; public class StateRestAPITest { + private static final ObjectMapper mapper = new ObjectMapper(); + private static void populateDummyBackend(DummyBackend backend) { backend.addCluster(new DummyBackend.Cluster("foo") .addNode(new DummyBackend.Node("1") @@ -61,14 +74,15 @@ public class StateRestAPITest { if (!op.isSuccess()) { // Don't call getCause() unless it fails assertTrue(op.isSuccess(), op.getCause().toString()); } - assertTrue(op.getResult() != null); + assertNotNull(op.getResult()); return op.getResult(); } - private JSONObject executeOkJsonRequest(HttpRequest request) { + + private JsonNode executeOkJsonRequest(HttpRequest request) { HttpResult result = execute(request); assertEquals(200, result.getHttpReturnCode(), result.toString(true)); assertEquals("application/json", result.getHeader("Content-Type"), result.toString(true)); - return (JSONObject) result.getContent(); + return (JsonNode) result.getContent(); } @Test @@ -77,11 +91,18 @@ public class StateRestAPITest { HttpResult result = execute(new HttpRequest().setPath("/cluster/v2")); assertEquals(200, result.getHttpReturnCode(), result.toString(true)); assertEquals("application/json", result.getHeader("Content-Type"), result.toString(true)); - String expected = "{\"cluster\": {\n" - + " \"foo\": {\"link\": \"\\/cluster\\/v2\\/foo\"},\n" - + " \"bar\": {\"link\": \"\\/cluster\\/v2\\/bar\"}\n" - + "}}"; - assertEquals(expected, ((JSONObject) result.getContent()).toString(2)); + assertEquals(""" + { + "cluster" : { + "foo" : { + "link" : "/cluster/v2/foo" + }, + "bar" : { + "link" : "/cluster/v2/bar" + } + } + }""", + ((JsonNode) result.getContent()).toPrettyString()); } @Test @@ -90,11 +111,18 @@ public class StateRestAPITest { HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo")); assertEquals(200, result.getHttpReturnCode(), result.toString(true)); assertEquals("application/json", result.getHeader("Content-Type"), result.toString(true)); - String expected = "{\"node\": {\n" - + " \"1\": {\"link\": \"\\/cluster\\/v2\\/foo\\/1\"},\n" - + " \"3\": {\"link\": \"\\/cluster\\/v2\\/foo\\/3\"}\n" - + "}}"; - assertEquals(expected, ((JSONObject) result.getContent()).toString(2)); + assertEquals(""" + { + "node" : { + "1" : { + "link" : "/cluster/v2/foo/1" + }, + "3" : { + "link" : "/cluster/v2/foo/3" + } + } + }""", + ((JsonNode) result.getContent()).toPrettyString()); } @Test @@ -103,118 +131,170 @@ public class StateRestAPITest { HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo/3")); assertEquals(200, result.getHttpReturnCode(), result.toString(true)); assertEquals("application/json", result.getHeader("Content-Type"), result.toString(true)); - String expected = "{\n" - + " \"attributes\": {\"group\": \"mygroup\"},\n" - + " \"state\": {\"current\": {\n" - + " \"state\": \"up\",\n" - + " \"reason\": \"\"\n" - + " }},\n" - + " \"metrics\": {\"doc-count\": 8}\n" - + "}"; - assertEquals(expected, ((JSONObject) result.getContent()).toString(2)); + String expected = """ + { + "attributes" : { + "group" : "mygroup" + }, + "state" : { + "current" : { + "state" : "up", + "reason" : "" + } + }, + "metrics" : { + "doc-count" : 8 + } + }"""; + assertEquals(expected, ((JsonNode) result.getContent()).toPrettyString()); } @Test void testRecursiveMode() throws Exception { setupDummyStateApi(); { - JSONObject json = executeOkJsonRequest( + JsonNode json = executeOkJsonRequest( new HttpRequest().setPath("/cluster/v2").addUrlOption("recursive", "true")); - String expected = - "{\"cluster\": {\n" + - " \"foo\": {\"node\": {\n" + - " \"1\": {\n" + - " \"attributes\": {\"group\": \"mygroup\"},\n" + - " \"state\": {\"current\": {\n" + - " \"state\": \"initializing\",\n" + - " \"reason\": \"\"\n" + - " }},\n" + - " \"metrics\": {\"doc-count\": 5}\n" + - " },\n" + - " \"3\": {\n" + - " \"attributes\": {\"group\": \"mygroup\"},\n" + - " \"state\": {\"current\": {\n" + - " \"state\": \"up\",\n" + - " \"reason\": \"\"\n" + - " }},\n" + - " \"metrics\": {\"doc-count\": 8}\n" + - " }\n" + - " }},\n" + - " \"bar\": {\"node\": {\"2\": {\n" + - " \"attributes\": {\"group\": \"mygroup\"},\n" + - " \"state\": {\"current\": {\n" + - " \"state\": \"down\",\n" + - " \"reason\": \"\"\n" + - " }},\n" + - " \"metrics\": {\"doc-count\": 0}\n" + - " }}}\n" + - "}}"; - assertEquals(expected, json.toString(2)); + assertEquals(""" + { + "cluster" : { + "foo" : { + "node" : { + "1" : { + "attributes" : { + "group" : "mygroup" + }, + "state" : { + "current" : { + "state" : "initializing", + "reason" : "" + } + }, + "metrics" : { + "doc-count" : 5 + } + }, + "3" : { + "attributes" : { + "group" : "mygroup" + }, + "state" : { + "current" : { + "state" : "up", + "reason" : "" + } + }, + "metrics" : { + "doc-count" : 8 + } + } + } + }, + "bar" : { + "node" : { + "2" : { + "attributes" : { + "group" : "mygroup" + }, + "state" : { + "current" : { + "state" : "down", + "reason" : "" + } + }, + "metrics" : { + "doc-count" : 0 + } + } + } + } + } + }""", + json.toPrettyString()); } { - JSONObject json = executeOkJsonRequest( - new HttpRequest().setPath("/cluster/v2").addUrlOption("recursive", "1")); - String expected = - "{\"cluster\": {\n" + - " \"foo\": {\"node\": {\n" + - " \"1\": {\"link\": \"\\/cluster\\/v2\\/foo\\/1\"},\n" + - " \"3\": {\"link\": \"\\/cluster\\/v2\\/foo\\/3\"}\n" + - " }},\n" + - " \"bar\": {\"node\": {\"2\": {\"link\": \"\\/cluster\\/v2\\/bar\\/2\"}}}\n" + - "}}"; - // Verify that the actual link does not contain backslash. It's just an artifact of - // jettison json output. - assertEquals("/cluster/v2/foo/1", - json.getJSONObject("cluster").getJSONObject("foo").getJSONObject("node") - .getJSONObject("1").getString("link")); - assertEquals(expected, json.toString(2)); + JsonNode json = executeOkJsonRequest(new HttpRequest().setPath("/cluster/v2").addUrlOption("recursive", "1")); + assertEquals(""" + { + "cluster" : { + "foo" : { + "node" : { + "1" : { + "link" : "/cluster/v2/foo/1" + }, + "3" : { + "link" : "/cluster/v2/foo/3" + } + } + }, + "bar" : { + "node" : { + "2" : { + "link" : "/cluster/v2/bar/2" + } + } + } + } + }""", + json.toPrettyString()); } { - JSONObject json = executeOkJsonRequest( - new HttpRequest().setPath("/cluster/v2/foo").addUrlOption("recursive", "1")); - String expected = - "{\"node\": {\n" + - " \"1\": {\n" + - " \"attributes\": {\"group\": \"mygroup\"},\n" + - " \"state\": {\"current\": {\n" + - " \"state\": \"initializing\",\n" + - " \"reason\": \"\"\n" + - " }},\n" + - " \"metrics\": {\"doc-count\": 5}\n" + - " },\n" + - " \"3\": {\n" + - " \"attributes\": {\"group\": \"mygroup\"},\n" + - " \"state\": {\"current\": {\n" + - " \"state\": \"up\",\n" + - " \"reason\": \"\"\n" + - " }},\n" + - " \"metrics\": {\"doc-count\": 8}\n" + - " }\n" + - "}}"; - assertEquals(expected, json.toString(2)); + JsonNode json = executeOkJsonRequest( new HttpRequest().setPath("/cluster/v2/foo").addUrlOption("recursive", "1")); + assertEquals(""" + { + "node" : { + "1" : { + "attributes" : { + "group" : "mygroup" + }, + "state" : { + "current" : { + "state" : "initializing", + "reason" : "" + } + }, + "metrics" : { + "doc-count" : 5 + } + }, + "3" : { + "attributes" : { + "group" : "mygroup" + }, + "state" : { + "current" : { + "state" : "up", + "reason" : "" + } + }, + "metrics" : { + "doc-count" : 8 + } + } + } + }""", json.toPrettyString()); } { - JSONObject json = executeOkJsonRequest( - new HttpRequest().setPath("/cluster/v2/foo").addUrlOption("recursive", "false")); - String expected = - "{\"node\": {\n" + - " \"1\": {\"link\": \"\\/cluster\\/v2\\/foo\\/1\"},\n" + - " \"3\": {\"link\": \"\\/cluster\\/v2\\/foo\\/3\"}\n" + - "}}"; - assertEquals(expected, json.toString(2)); + JsonNode json = executeOkJsonRequest( new HttpRequest().setPath("/cluster/v2/foo").addUrlOption("recursive", "false")); + assertEquals(""" + { + "node" : { + "1" : { + "link" : "/cluster/v2/foo/1" + }, + "3" : { + "link" : "/cluster/v2/foo/3" + } + } + }""", json.toPrettyString()); } } private String retireAndExpectHttp200Response(Optional<String> responseWait) throws Exception { - JSONObject json = new JSONObject() - .put("state", new JSONObject() - .put("current", new JSONObject() - .put("state", "retired") - .put("reason", "No reason"))) - .put("condition", "FORCE"); - if (responseWait.isPresent()) { - json.put("response-wait", responseWait.get()); - } + ObjectNode json = new ObjectNode(mapper.getNodeFactory()); + json.putObject("state").putObject("current").put("state", "retired").put("reason", "No reason"); + json.put("condition", "FORCE"); + responseWait.ifPresent(wait -> json.put("response-wait", wait)); HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo/3").setPostContent(json)); assertEquals(200, result.getHttpReturnCode(), result.toString(true)); assertEquals("application/json", result.getHeader("Content-Type"), result.toString(true)); @@ -227,44 +307,56 @@ public class StateRestAPITest { void testSetNodeState() throws Exception { setupDummyStateApi(); { - JSONObject json = new JSONObject().put("state", new JSONObject() - .put("current", new JSONObject() - .put("state", "retired") - .put("reason", "No reason"))); + ObjectNode json = new ObjectNode(mapper.getNodeFactory()); + json.putObject("state").putObject("current").put("state", "retired").put("reason", "No reason"); HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo/3").setPostContent(json)); assertEquals(200, result.getHttpReturnCode(), result.toString(true)); assertEquals("application/json", result.getHeader("Content-Type"), result.toString(true)); } { - JSONObject json = executeOkJsonRequest(new HttpRequest().setPath("/cluster/v2/foo/3")); - String expected = "{\n" - + " \"attributes\": {\"group\": \"mygroup\"},\n" - + " \"state\": {\"current\": {\n" - + " \"state\": \"retired\",\n" - + " \"reason\": \"No reason\"\n" - + " }},\n" - + " \"metrics\": {\"doc-count\": 8}\n" - + "}"; - assertEquals(expected, json.toString(2), json.toString(2)); + JsonNode json = executeOkJsonRequest(new HttpRequest().setPath("/cluster/v2/foo/3")); + assertEquals(""" + { + "attributes" : { + "group" : "mygroup" + }, + "state" : { + "current" : { + "state" : "retired", + "reason" : "No reason" + } + }, + "metrics" : { + "doc-count" : 8 + } + }""", + json.toPrettyString()); } { - JSONObject json = new JSONObject().put("state", new JSONObject() - .put("current", new JSONObject())); + ObjectNode json = new ObjectNode(mapper.getNodeFactory()); + json.putObject("state").putObject("current"); HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo/3").setPostContent(json)); assertEquals(200, result.getHttpReturnCode(), result.toString(true)); assertEquals("application/json", result.getHeader("Content-Type"), result.toString(true)); } { - JSONObject json = executeOkJsonRequest(new HttpRequest().setPath("/cluster/v2/foo/3")); - String expected = "{\n" - + " \"attributes\": {\"group\": \"mygroup\"},\n" - + " \"state\": {\"current\": {\n" - + " \"state\": \"up\",\n" - + " \"reason\": \"\"\n" - + " }},\n" - + " \"metrics\": {\"doc-count\": 8}\n" - + "}"; - assertEquals(expected, json.toString(2), json.toString(2)); + JsonNode json = executeOkJsonRequest(new HttpRequest().setPath("/cluster/v2/foo/3")); + assertEquals(""" + { + "attributes" : { + "group" : "mygroup" + }, + "state" : { + "current" : { + "state" : "up", + "reason" : "" + } + }, + "metrics" : { + "doc-count" : 8 + } + }""", + json.toPrettyString()); } } @@ -273,19 +365,21 @@ public class StateRestAPITest { setupDummyStateApi(); { String result = retireAndExpectHttp200Response(Optional.of("wait-until-cluster-acked")); - assertEquals(result, - "JSON: {\n" + - " \"wasModified\": true,\n" + - " \"reason\": \"DummyStateAPI wait-until-cluster-acked call\"\n" + - "}"); + assertEquals(""" + JSON: { + "wasModified" : true, + "reason" : "DummyStateAPI wait-until-cluster-acked call" + }""", + result); } { String result = retireAndExpectHttp200Response(Optional.of("no-wait")); - assertEquals(result, - "JSON: {\n" + - " \"wasModified\": true,\n" + - " \"reason\": \"DummyStateAPI no-wait call\"\n" + - "}"); + assertEquals(""" + JSON: { + "wasModified" : true, + "reason" : "DummyStateAPI no-wait call" + }""", + result); } } @@ -293,11 +387,12 @@ public class StateRestAPITest { void set_node_state_response_wait_type_is_cluster_acked_by_default() throws Exception { setupDummyStateApi(); String result = retireAndExpectHttp200Response(Optional.empty()); - assertEquals(result, - "JSON: {\n" + - " \"wasModified\": true,\n" + - " \"reason\": \"DummyStateAPI wait-until-cluster-acked call\"\n" + - "}"); + assertEquals(""" + JSON: { + "wasModified" : true, + "reason" : "DummyStateAPI wait-until-cluster-acked call" + }""", + result); } @Test @@ -314,7 +409,7 @@ public class StateRestAPITest { HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo/1234")); assertEquals(404, result.getHttpReturnCode(), result.toString(true)); assertEquals("No such resource 'foo/1234'.", result.getHttpReturnCodeDescription(), result.toString(true)); - String expected = "{\"message\":\"No such resource 'foo\\/1234'.\"}"; + String expected = "{\"message\":\"No such resource 'foo/1234'.\"}"; assertEquals(expected, result.getContent().toString()); } } @@ -442,7 +537,7 @@ public class StateRestAPITest { void testInvalidJsonInSetStateRequest() throws Exception { setupDummyStateApi(); { - JSONObject json = new JSONObject(); + ObjectNode json = new ObjectNode(mapper.getNodeFactory()); HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo/3").setPostContent(json)); assertEquals(400, result.getHttpReturnCode(), result.toString(true)); assertEquals("Content of HTTP request had invalid data", result.getHttpReturnCodeDescription(), result.toString(true)); @@ -450,7 +545,7 @@ public class StateRestAPITest { assertTrue(result.getContent().toString().contains("Set state requests must contain a state object"), result.toString(true)); } { - JSONObject json = new JSONObject().put("state", 5); + ObjectNode json = new ObjectNode(mapper.getNodeFactory()).put("state", 5); HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo/3").setPostContent(json)); assertEquals(400, result.getHttpReturnCode(), result.toString(true)); assertEquals("Content of HTTP request had invalid data", result.getHttpReturnCodeDescription(), result.toString(true)); @@ -458,8 +553,8 @@ public class StateRestAPITest { assertTrue(result.getContent().toString().contains("value of state is not a json object"), result.toString(true)); } { - JSONObject json = new JSONObject().put("state", new JSONObject() - .put("current", 5)); + ObjectNode json = new ObjectNode(mapper.getNodeFactory()); + json.putObject("state").put("current", 5); HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo/3").setPostContent(json)); assertEquals(400, result.getHttpReturnCode(), result.toString(true)); assertEquals("Content of HTTP request had invalid data", result.getHttpReturnCodeDescription(), result.toString(true)); @@ -467,8 +562,8 @@ public class StateRestAPITest { assertTrue(result.getContent().toString().contains("value of state->current is not a json object"), result.toString(true)); } { - JSONObject json = new JSONObject().put("state", new JSONObject() - .put("current", new JSONObject().put("state", 5))); + ObjectNode json = new ObjectNode(mapper.getNodeFactory()); + json.putObject("state").putObject("current").put("state", 5); HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo/3").setPostContent(json)); assertEquals(400, result.getHttpReturnCode(), result.toString(true)); assertEquals("Content of HTTP request had invalid data", result.getHttpReturnCodeDescription(), result.toString(true)); @@ -476,8 +571,8 @@ public class StateRestAPITest { assertTrue(result.getContent().toString().contains("value of state->current->state is not a string"), result.toString(true)); } { - JSONObject json = new JSONObject().put("state", new JSONObject() - .put("current", new JSONObject().put("state", "down").put("reason", 5))); + ObjectNode json = new ObjectNode(mapper.getNodeFactory()); + json.putObject("state").putObject("current").put("state", "down").put("reason", 5); HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo/3").setPostContent(json)); assertEquals(400, result.getHttpReturnCode(), result.toString(true)); assertEquals("Content of HTTP request had invalid data", result.getHttpReturnCodeDescription(), result.toString(true)); @@ -486,24 +581,27 @@ public class StateRestAPITest { } { String result = retireAndExpectHttp400Response("Non existing condition", "no-wait"); - assertEquals(result, - "JSON: {\"message\": \"Invalid value for condition: 'Non existing condition', expected one of 'force', 'safe'\"}"); + assertEquals(""" + JSON: { + "message" : "Invalid value for condition: 'Non existing condition', expected one of 'force', 'safe'" + }""", + result); } { String result = retireAndExpectHttp400Response("FORCE", "banana"); - assertEquals(result, - "JSON: {\"message\": \"Invalid value for response-wait: 'banana', expected one of 'wait-until-cluster-acked', 'no-wait'\"}"); + assertEquals(""" + JSON: { + "message" : "Invalid value for response-wait: 'banana', expected one of 'wait-until-cluster-acked', 'no-wait'" + }""", + result); } } private String retireAndExpectHttp400Response(String condition, String responseWait) throws Exception { - JSONObject json = new JSONObject() - .put("state", new JSONObject() - .put("current", new JSONObject() - .put("state", "retired") - .put("reason", "No reason"))) - .put("condition", condition) - .put("response-wait", responseWait); + ObjectNode json = new ObjectNode(mapper.getNodeFactory()); + json.putObject("state").putObject("current").put("state", "retired").put("reason", "No reason"); + json.put("condition", condition); + json.put("response-wait", responseWait); HttpResult result = execute(new HttpRequest().setPath("/cluster/v2/foo/3").setPostContent(json)); assertEquals(400, result.getHttpReturnCode(), result.toString(true)); assertEquals("application/json", result.getHeader("Content-Type"), result.toString(true)); diff --git a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/test/TestTransport.java b/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/test/TestTransport.java index ede7d00993e..7a334ae3e34 100644 --- a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/test/TestTransport.java +++ b/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/test/TestTransport.java @@ -59,7 +59,7 @@ public class TestTransport { } private final Map<Socket, List<Handler>> handlers = new HashMap<>(); private final LinkedList<Request> requests = new LinkedList<>(); - private final AsyncHttpClient<HttpResult> client = new AsyncHttpClient<HttpResult>() { + private final AsyncHttpClient<HttpResult> client = new AsyncHttpClient<>() { @Override public AsyncOperation<HttpResult> execute(HttpRequest r) { log.fine("Queueing request " + r); @@ -74,6 +74,7 @@ public class TestTransport { } return op; } + @Override public void close() { TestTransport.this.close(); } }; @@ -149,7 +150,7 @@ public class TestTransport { } log.fine("Socket found"); for (Handler h : handlers.get(socket)) { - if (r.getPath().length() >= h.pathPrefix.length() && r.getPath().substring(0, h.pathPrefix.length()).equals(h.pathPrefix)) { + if (r.getPath().length() >= h.pathPrefix.length() && r.getPath().startsWith(h.pathPrefix)) { return h.handler; } } diff --git a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/util/JSONObjectWrapperTest.java b/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/util/JSONObjectWrapperTest.java deleted file mode 100644 index f09907e7fcd..00000000000 --- a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/util/JSONObjectWrapperTest.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.clustercontroller.utils.util; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class JSONObjectWrapperTest { - - @Test - void testExceptionWrapping() { - JSONObjectWrapper wrapper = new JSONObjectWrapper(); - try { - wrapper.put(null, "foo"); - } catch (NullPointerException e) { - assertEquals("Null key.", e.getMessage()); - } - } - -} |