diff options
author | Martin Polden <martin.polden@gmail.com> | 2017-08-08 11:33:37 +0200 |
---|---|---|
committer | Martin Polden <martin.polden@gmail.com> | 2017-08-08 12:06:10 +0200 |
commit | abc6ff167b22fe3fae82d6ec369ddfd0ba7260af (patch) | |
tree | 42078775d9d144d76e7415c1a5b27caa8ec6a0c6 /node-repository | |
parent | a69f61901d6a243eec05d7a8d60eecbf28d70931 (diff) |
Add hardware divergence field to nodes API
Diffstat (limited to 'node-repository')
6 files changed, 116 insertions, 11 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Status.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Status.java index 16ce1c2f1d5..35e2a102737 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Status.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Status.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.node; import com.yahoo.component.Version; import javax.annotation.concurrent.Immutable; +import java.util.Objects; import java.util.Optional; /** @@ -20,6 +21,7 @@ public class Status { private final Optional<HardwareFailureType> hardwareFailure; private final boolean wantToRetire; private final boolean wantToDeprovision; + private final Optional<String> hardwareDivergence; public enum HardwareFailureType { @@ -37,44 +39,51 @@ public class Status { int failCount, Optional<HardwareFailureType> hardwareFailure, boolean wantToRetire, - boolean wantToDeprovision) { + boolean wantToDeprovision, + Optional<String> hardwareDivergence) { + Objects.requireNonNull(generation, "Generation must be non-null"); + Objects.requireNonNull(vespaVersion, "Vespa version must be non-null"); + Objects.requireNonNull(hardwareFailure, "Hardware failure must be non-null"); + Objects.requireNonNull(hardwareDivergence, "Hardware divergence must be non-null"); + hardwareDivergence.ifPresent(s -> requireNonEmptyString(s, "Hardware divergence must be non-empty")); this.reboot = generation; this.vespaVersion = vespaVersion; this.failCount = failCount; this.hardwareFailure = hardwareFailure; this.wantToRetire = wantToRetire; this.wantToDeprovision = wantToDeprovision; + this.hardwareDivergence = hardwareDivergence; } /** Returns a copy of this with the reboot generation changed */ - public Status withReboot(Generation reboot) { return new Status(reboot, vespaVersion, failCount, hardwareFailure, wantToRetire, wantToDeprovision); } + public Status withReboot(Generation reboot) { return new Status(reboot, vespaVersion, failCount, hardwareFailure, wantToRetire, wantToDeprovision, hardwareDivergence); } /** Returns the reboot generation of this node */ public Generation reboot() { return reboot; } /** Returns a copy of this with the vespa version changed */ - public Status withVespaVersion(Version version) { return new Status(reboot, Optional.of(version), failCount, hardwareFailure, wantToRetire, wantToDeprovision); } + public Status withVespaVersion(Version version) { return new Status(reboot, Optional.of(version), failCount, hardwareFailure, wantToRetire, wantToDeprovision, hardwareDivergence); } /** Returns the Vespa version installed on the node, if known */ public Optional<Version> vespaVersion() { return vespaVersion; } - public Status withIncreasedFailCount() { return new Status(reboot, vespaVersion, failCount + 1, hardwareFailure, wantToRetire, wantToDeprovision); } + public Status withIncreasedFailCount() { return new Status(reboot, vespaVersion, failCount + 1, hardwareFailure, wantToRetire, wantToDeprovision, hardwareDivergence); } - public Status withDecreasedFailCount() { return new Status(reboot, vespaVersion, failCount - 1, hardwareFailure, wantToRetire, wantToDeprovision); } + public Status withDecreasedFailCount() { return new Status(reboot, vespaVersion, failCount - 1, hardwareFailure, wantToRetire, wantToDeprovision, hardwareDivergence); } - public Status setFailCount(Integer value) { return new Status(reboot, vespaVersion, value, hardwareFailure, wantToRetire, wantToDeprovision); } + public Status setFailCount(Integer value) { return new Status(reboot, vespaVersion, value, hardwareFailure, wantToRetire, wantToDeprovision, hardwareDivergence); } /** Returns how many times this node has been moved to the failed state. */ public int failCount() { return failCount; } - public Status withHardwareFailure(Optional<HardwareFailureType> hardwareFailure) { return new Status(reboot, vespaVersion, failCount, hardwareFailure, wantToRetire, wantToDeprovision); } + public Status withHardwareFailure(Optional<HardwareFailureType> hardwareFailure) { return new Status(reboot, vespaVersion, failCount, hardwareFailure, wantToRetire, wantToDeprovision, hardwareDivergence); } /** Returns the type of the last hardware failure detected on this node, or empty if none */ public Optional<HardwareFailureType> hardwareFailure() { return hardwareFailure; } /** Returns a copy of this with the want to retire flag changed */ public Status withWantToRetire(boolean wantToRetire) { - return new Status(reboot, vespaVersion, failCount, hardwareFailure, wantToRetire, wantToDeprovision); + return new Status(reboot, vespaVersion, failCount, hardwareFailure, wantToRetire, wantToDeprovision, hardwareDivergence); } /** @@ -87,7 +96,7 @@ public class Status { /** Returns a copy of this with the want to de-provision flag changed */ public Status withWantToDeprovision(boolean wantToDeprovision) { - return new Status(reboot, vespaVersion, failCount, hardwareFailure, wantToRetire, wantToDeprovision); + return new Status(reboot, vespaVersion, failCount, hardwareFailure, wantToRetire, wantToDeprovision, hardwareDivergence); } /** @@ -97,7 +106,20 @@ public class Status { return wantToDeprovision; } + public Status withHardwareDivergence(Optional<String> hardwareDivergence) { + return new Status(reboot, vespaVersion, failCount, hardwareFailure, wantToRetire, wantToDeprovision, hardwareDivergence); + } + + /** Returns hardware divergence report as JSON string, if any */ + public Optional<String> hardwareDivergence() { return hardwareDivergence; } + /** Returns the initial status of a newly provisioned node */ - public static Status initial() { return new Status(Generation.inital(), Optional.empty(), 0, Optional.empty(), false, false); } + public static Status initial() { return new Status(Generation.inital(), Optional.empty(), 0, Optional.empty(), false, false, Optional.empty()); } + + private void requireNonEmptyString(String value, String message) { + Objects.requireNonNull(value, message); + if (value.trim().isEmpty()) + throw new IllegalArgumentException(message + ", but was '" + value + "'"); + } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java index c1541f00a66..9f876cf5b14 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java @@ -57,6 +57,7 @@ public class NodeSerializer { private static final String nodeTypeKey = "type"; private static final String wantToRetireKey = "wantToRetire"; private static final String wantToDeprovisionKey = "wantToDeprovision"; + private static final String hardwareDivergenceKey = "hardwareDivergence"; // Configuration fields private static final String flavorKey = "flavor"; @@ -111,6 +112,8 @@ public class NodeSerializer { node.allocation().ifPresent(allocation -> toSlime(allocation, object.setObject(instanceKey))); toSlime(node.history(), object.setArray(historyKey)); object.setString(nodeTypeKey, toString(node.type())); + node.status().hardwareDivergence().ifPresent(hardwareDivergence -> object.setString(hardwareDivergenceKey, + hardwareDivergence)); } private void toSlime(Allocation allocation, Cursor object) { @@ -167,7 +170,8 @@ public class NodeSerializer { (int)object.field(failCountKey).asLong(), hardwareFailureFromSlime(object.field(hardwareFailureKey)), object.field(wantToRetireKey).asBool(), - wantToDeprovision); + wantToDeprovision, + hardwareDivergenceFromSlime(object)); } private Flavor flavorFromSlime(Inspector object) { @@ -228,6 +232,13 @@ public class NodeSerializer { return Optional.empty(); } + private Optional<String> hardwareDivergenceFromSlime(Inspector object) { + if (object.field(hardwareDivergenceKey).valid()) { + return Optional.of(object.field(hardwareDivergenceKey).asString()); + } + return Optional.empty(); + } + private Set<String> ipAddressesFromSlime(Inspector object, String key) { ImmutableSet.Builder<String> ipAddresses = ImmutableSet.builder(); object.field(key).traverse((ArrayTraverser) (i, item) -> ipAddresses.add(item.asString())); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodePatcher.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodePatcher.java index 9e7b178c2bc..07e005a3601 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodePatcher.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodePatcher.java @@ -124,6 +124,11 @@ public class NodePatcher { return node.with(node.status().withWantToRetire(asBoolean(value))); case "wantToDeprovision" : return node.with(node.status().withWantToDeprovision(asBoolean(value))); + case "hardwareDivergence" : + if (value.type().equals(Type.NIX)) { + return node.with(node.status().withHardwareDivergence(Optional.empty())); + } + return node.with(node.status().withHardwareDivergence(Optional.of(asString(value)))); default : throw new IllegalArgumentException("Could not apply field '" + name + "' on a node: No such modifiable field"); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java index 2a844bdc21b..699782e3a93 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java @@ -178,6 +178,7 @@ class NodesResponse extends HttpResponse { toSlime(node.history(), object.setArray("history")); ipAddressesToSlime(node.ipAddresses(), object.setArray("ipAddresses")); ipAddressesToSlime(node.additionalIpAddresses(), object.setArray("additionalIpAddresses")); + node.status().hardwareDivergence().ifPresent(hardwareDivergence -> object.setString("hardwareDivergence", hardwareDivergence)); } private String toString(NodeType type) { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java index 53b14c2e1ec..dd2b64a7854 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/RestApiTest.java @@ -471,6 +471,30 @@ public class RestApiTest { assertFile(new Request("http://localhost:8080/nodes/v2/node/host5.yahoo.com"), "node5-after-changes.json"); } + @Test + public void test_hardware_divergence_patching() throws Exception { + // Add report + assertResponse(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com", + Utf8.toBytes("{\"hardwareDivergence\": \"{\\\"actualCpuCores\\\":2}\"}"), + Request.Method.PATCH), + "{\"message\":\"Updated host6.yahoo.com\"}"); + assertFile(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com"), "node6-after-changes.json"); + + // Empty report is rejected + assertResponse(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com", + Utf8.toBytes("{\"hardwareDivergence\": \"\"}"), + Request.Method.PATCH), + 400, + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not set field 'hardwareDivergence': Hardware divergence must be non-empty, but was ''\"}"); + + // Clear report + assertResponse(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com", + Utf8.toBytes("{\"hardwareDivergence\": null}"), + Request.Method.PATCH), + "{\"message\":\"Updated host6.yahoo.com\"}"); + assertFile(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com"), "node6.json"); + } + /** Tests the rendering of each node separately to make it easier to find errors */ @Test public void test_single_node_rendering() throws Exception { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node6-after-changes.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node6-after-changes.json new file mode 100644 index 00000000000..7177567011a --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/node6-after-changes.json @@ -0,0 +1,42 @@ +{ + "url": "http://localhost:8080/nodes/v2/node/host6.yahoo.com", + "id": "host6.yahoo.com", + "state": "active", + "type": "tenant", + "hostname": "host6.yahoo.com", + "openStackId": "node6", + "flavor": "default", + "canonicalFlavor": "default", + "minDiskAvailableGb":400.0, + "minMainMemoryAvailableGb":16.0, + "description":"Flavor-name-is-default", + "minCpuCores":2.0, + "fastDisk":true, + "environment":"BARE_METAL", + "owner": { + "tenant": "tenant3", + "application": "application3", + "instance": "instance3" + }, + "membership": { + "clustertype": "content", + "clusterid": "id3", + "group": "0", + "index": 0, + "retired": false + }, + "restartGeneration": 0, + "currentRestartGeneration": 0, + "wantedDockerImage":"docker-registry.domain.tld:8080/dist/vespa:6.42.0", + "wantedVespaVersion":"6.42.0", + "rebootGeneration": 1, + "currentRebootGeneration": 0, + "failCount": 0, + "hardwareFailure" : false, + "wantToRetire" : false, + "wantToDeprovision" : false, + "history":[{"event":"readied","at":123,"agent":"system"},{"event":"reserved","at":123,"agent":"application"},{"event":"activated","at":123,"agent":"application"}], + "ipAddresses":["::1", "127.0.0.1"], + "additionalIpAddresses":[], + "hardwareDivergence":"{\"actualCpuCores\":2}" +} |