diff options
author | Martin Polden <mpolden@mpolden.no> | 2020-11-27 11:43:33 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2020-11-27 14:06:43 +0100 |
commit | 3cf13b61b9ddc7b9e623415db006b4cbfc2ce8cd (patch) | |
tree | d17d6126d30a01abcc1abef4b8c3d340e1ced6c1 /config-provisioning | |
parent | e1cd9737955d4521b2b90f5fd341af2fe608b184 (diff) |
Read stateful tag in ClusterMembership
Diffstat (limited to 'config-provisioning')
3 files changed, 83 insertions, 36 deletions
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java index 45465fa91c4..9fb1689782f 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java @@ -3,11 +3,12 @@ package com.yahoo.config.provision; import com.yahoo.component.Version; +import java.util.Objects; import java.util.Optional; /** * A node's membership in a cluster. This is a value object. - * The format is "clusterType/clusterId/groupId/index[/exclusive][/retired][/combinedId]" + * The format is "clusterType/clusterId/groupId/index[/exclusive][/retired][/stateful][/combinedId]" * * @author bratseth */ @@ -24,9 +25,10 @@ public class ClusterMembership { String[] components = stringValue.split("/"); if (components.length < 4) throw new RuntimeException("Could not parse '" + stringValue + "' to a cluster membership. " + - "Expected 'clusterType/clusterId/groupId/index[/retired][/exclusive][/combinedId]'"); + "Expected 'clusterType/clusterId/groupId/index[/retired][/exclusive][/stateful][/combinedId]'"); boolean exclusive = false; + boolean stateful = false; var combinedId = Optional.<String>empty(); if (components.length > 4) { for (int i = 4; i < components.length; i++) { @@ -34,18 +36,24 @@ public class ClusterMembership { switch (component) { case "exclusive": exclusive = true; break; case "retired": retired = true; break; + case "stateful": stateful = true; break; default: combinedId = Optional.of(component); break; } } } - this.cluster = ClusterSpec.specification(ClusterSpec.Type.valueOf(components[0]), ClusterSpec.Id.from(components[1])) - .group(ClusterSpec.Group.from(Integer.parseInt(components[2]))) - .vespaVersion(vespaVersion) - .exclusive(exclusive) - .combinedId(combinedId.map(ClusterSpec.Id::from)) - .dockerImageRepository(dockerImageRepo) - .build(); + ClusterSpec.Type type = ClusterSpec.Type.valueOf(components[0]); + if (type.isContent()) { + stateful = true; // TODO(mpolden): Serialization compatibility. Remove after December 2020 + } + this.cluster = ClusterSpec.specification(type, ClusterSpec.Id.from(components[1])) + .group(ClusterSpec.Group.from(Integer.parseInt(components[2]))) + .vespaVersion(vespaVersion) + .exclusive(exclusive) + .combinedId(combinedId.map(ClusterSpec.Id::from)) + .dockerImageRepository(dockerImageRepo) + .stateful(stateful) + .build(); this.index = Integer.parseInt(components[3]); this.stringValue = toStringValue(); } @@ -64,6 +72,8 @@ public class ClusterMembership { "/" + index + ( cluster.isExclusive() ? "/exclusive" : "") + ( retired ? "/retired" : "") + + // TODO(mpolden): Write stateful tag once all nodes can read it + // ( cluster.isStateful() ? "/stateful" : "") + ( cluster.combinedId().isPresent() ? "/" + cluster.combinedId().get().value() : ""); } @@ -98,13 +108,19 @@ public class ClusterMembership { public String stringValue() { return stringValue; } @Override - public int hashCode() { return stringValue().hashCode(); } + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClusterMembership that = (ClusterMembership) o; + return index == that.index && + retired == that.retired && + cluster.equals(that.cluster) && + stringValue.equals(that.stringValue); + } @Override - public boolean equals(Object other) { - if (other == this) return true; - if ( ! (other instanceof ClusterMembership)) return false; - return ((ClusterMembership)other).stringValue().equals(stringValue()); + public int hashCode() { + return Objects.hash(cluster, index, retired, stringValue); } @Override 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 7d2a96ce991..e455c9d2ed4 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 @@ -23,9 +23,10 @@ public final class ClusterSpec { private final boolean exclusive; private final Optional<Id> combinedId; private final Optional<DockerImage> dockerImageRepo; + private final boolean stateful; private ClusterSpec(Type type, Id id, Optional<Group> groupId, Version vespaVersion, boolean exclusive, - Optional<Id> combinedId, Optional<DockerImage> dockerImageRepo) { + Optional<Id> combinedId, Optional<DockerImage> dockerImageRepo, boolean stateful) { this.type = type; this.id = id; this.groupId = groupId; @@ -40,6 +41,10 @@ public final class ClusterSpec { if (dockerImageRepo.isPresent() && dockerImageRepo.get().tag().isPresent()) throw new IllegalArgumentException("dockerImageRepo is not allowed to have a tag"); this.dockerImageRepo = dockerImageRepo; + if (type.isContent() && !stateful) { + throw new IllegalArgumentException("Cluster of type " + type + " must be stateful"); + } + this.stateful = stateful; } /** Returns the cluster type */ @@ -71,12 +76,17 @@ public final class ClusterSpec { */ public boolean isExclusive() { return exclusive; } + /** 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); + return new ClusterSpec(type, id, newGroup, vespaVersion, exclusive, combinedId, dockerImageRepo, stateful); } public ClusterSpec exclusive(boolean exclusive) { - return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo); + return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, stateful); } /** Creates a ClusterSpec when requesting a cluster */ @@ -94,6 +104,7 @@ public final class ClusterSpec { private final Type type; private final Id id; private final boolean specification; + private boolean stateful; private Optional<Group> groupId = Optional.empty(); private Optional<DockerImage> dockerImageRepo = Optional.empty(); @@ -105,6 +116,7 @@ public final class ClusterSpec { this.type = type; this.id = id; this.specification = specification; + this.stateful = type.isContent(); // Default to true for content clusters } public ClusterSpec build() { @@ -113,7 +125,7 @@ public final class ClusterSpec { if (vespaVersion == null) throw new IllegalArgumentException("vespaVersion is required to be set when creating a ClusterSpec with specification()"); } else if (groupId.isPresent()) throw new IllegalArgumentException("groupId is not allowed to be set when creating a ClusterSpec with request()"); - return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo); + return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, stateful); } public Builder group(Group groupId) { @@ -146,6 +158,11 @@ public final class ClusterSpec { return this; } + public Builder stateful(boolean stateful) { + this.stateful = stateful; + return this; + } + } @Override @@ -154,19 +171,23 @@ public final class ClusterSpec { } @Override - public int hashCode() { return type.hashCode() + 17 * id.hashCode() + 31 * groupId.hashCode(); } + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClusterSpec that = (ClusterSpec) o; + return exclusive == that.exclusive && + stateful == that.stateful && + type == that.type && + id.equals(that.id) && + groupId.equals(that.groupId) && + vespaVersion.equals(that.vespaVersion) && + combinedId.equals(that.combinedId) && + dockerImageRepo.equals(that.dockerImageRepo); + } @Override - public boolean equals(Object o) { - if (o == this) return true; - if ( ! (o instanceof ClusterSpec)) return false; - ClusterSpec other = (ClusterSpec)o; - if ( ! other.type.equals(this.type)) return false; - if ( ! other.id.equals(this.id)) return false; - if ( ! other.groupId.equals(this.groupId)) return false; - if ( ! other.vespaVersion.equals(this.vespaVersion)) return false; - if ( ! other.dockerImageRepo.equals(this.dockerImageRepo)) return false; - return true; + public int hashCode() { + return Objects.hash(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, stateful); } /** diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/ClusterMembershipTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/ClusterMembershipTest.java index 71e039f6e8e..1f170cca9a0 100644 --- a/config-provisioning/src/test/java/com/yahoo/config/provision/ClusterMembershipTest.java +++ b/config-provisioning/src/test/java/com/yahoo/config/provision/ClusterMembershipTest.java @@ -26,31 +26,41 @@ public class ClusterMembershipTest { { ClusterMembership instance = ClusterMembership.from("container/id1/4/37/exclusive/retired", Vtag.currentVersion, Optional.empty()); ClusterMembership serialized = ClusterMembership.from(instance.stringValue(), Vtag.currentVersion, Optional.empty()); + assertFalse(serialized.cluster().isStateful()); assertEquals(instance, serialized); assertTrue(instance.retired()); assertTrue(instance.cluster().isExclusive()); - assertFalse(instance.cluster().combinedId().isPresent()); - assertTrue(instance.cluster().dockerImageRepo().isEmpty()); } { ClusterMembership instance = ClusterMembership.from("container/id1/4/37/exclusive", Vtag.currentVersion, Optional.empty()); ClusterMembership serialized = ClusterMembership.from(instance.stringValue(), Vtag.currentVersion, Optional.empty()); + assertFalse(serialized.cluster().isStateful()); assertEquals(instance, serialized); - assertFalse(instance.retired()); assertTrue(instance.cluster().isExclusive()); - assertFalse(instance.cluster().combinedId().isPresent()); - assertTrue(instance.cluster().dockerImageRepo().isEmpty()); } { Optional<DockerImage> dockerImageRepo = Optional.of(DockerImage.fromString("docker.foo.com:4443/vespa/bar")); ClusterMembership instance = ClusterMembership.from("combined/id1/4/37/exclusive/containerId1", Vtag.currentVersion, dockerImageRepo); ClusterMembership serialized = ClusterMembership.from(instance.stringValue(), Vtag.currentVersion, dockerImageRepo); + assertTrue(serialized.cluster().isStateful()); assertEquals(instance, serialized); - assertFalse(instance.retired()); - assertTrue(instance.cluster().isExclusive()); assertEquals(ClusterSpec.Id.from("containerId1"), instance.cluster().combinedId().get()); assertEquals(dockerImageRepo.get(), instance.cluster().dockerImageRepo().get()); } + { + ClusterMembership instance = ClusterMembership.from("container/id1/4/37", Vtag.currentVersion, Optional.empty()); + ClusterMembership serialized = ClusterMembership.from(instance.stringValue(), Vtag.currentVersion, Optional.empty()); + assertEquals(instance, serialized); + assertFalse("Skips serialization of stateful property", instance.cluster().isStateful()); + } + } + + @Test + public void testLegacyFormat() { // TODO(mpolden): Remove after December 2020 + ClusterMembership instance = ClusterMembership.from("content/id1/4/37/exclusive", Vtag.currentVersion, Optional.empty()); + ClusterMembership serialized = ClusterMembership.from(instance.stringValue(), Vtag.currentVersion, Optional.empty()); + assertEquals(instance, serialized); + assertTrue(serialized.cluster().isStateful()); } @Test |