From 88fa4649fc79442d3d8e7c3254098f915a2e5889 Mon Sep 17 00:00:00 2001 From: Valerij Fredriksen Date: Wed, 2 May 2018 11:27:05 +0200 Subject: Create persistence for infrastructure versions --- .../maintenance/InfrastructureVersions.java | 63 ++++++++++++++++ .../persistence/CuratorDatabaseClient.java | 28 +++++++- .../InfrastructureVersionsSerializer.java | 42 +++++++++++ .../provision/persistence/NodeSerializer.java | 4 +- .../maintenance/InfrastructureVersionsTest.java | 83 ++++++++++++++++++++++ 5 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersions.java create mode 100644 node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/InfrastructureVersionsSerializer.java create mode 100644 node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersionsTest.java (limited to 'node-repository') diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersions.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersions.java new file mode 100644 index 00000000000..b90ec492e1a --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersions.java @@ -0,0 +1,63 @@ +package com.yahoo.vespa.hosted.provision.maintenance; + +import com.yahoo.component.Version; +import com.yahoo.config.provision.NodeType; +import com.yahoo.vespa.curator.Lock; +import com.yahoo.vespa.hosted.provision.persistence.CuratorDatabaseClient; + +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * Multithread safe class to see and set target versions for infrastructure node types. + * {@link InfrastructureProvisioner} maintainer will then allocate the nodes of given node type + * with a wantedVersionVersion equal to the given target version. + * + * @author freva + */ +public class InfrastructureVersions { + + private static Logger logger = Logger.getLogger(InfrastructureVersions.class.getName()); + + private final CuratorDatabaseClient db; + + public InfrastructureVersions(CuratorDatabaseClient db) { + this.db = db; + } + + public void setTargetVersion(NodeType nodeType, Version version, boolean force) { + if (nodeType != NodeType.config && nodeType != NodeType.confighost && nodeType != NodeType.proxyhost) { + throw new IllegalArgumentException("Cannot set version for type " + nodeType); + } + + try (Lock lock = db.lockInfrastructureVersions()) { + Map infrastructureVersions = db.readInfrastructureVersions(); + Optional currentWantedVersion = Optional.ofNullable(infrastructureVersions.get(nodeType)); + + // Trying to set the version to the current version, skip + if (currentWantedVersion.equals(Optional.of(version))) return; + + // If we don't force the set, we must set the new version to higher than the already set version + if (!force) { + if (currentWantedVersion.map(curVersion -> curVersion.compareTo(version) > 0).orElse(false)) + throw new IllegalArgumentException(String.format("Cannot downgrade version without setting 'force'. " + + "Current wanted version: %s, attempted to set wanted version: %s", + currentWantedVersion.get().toFullString(), version.toFullString())); + } + + infrastructureVersions.put(nodeType, version); + db.writeInfrastructureVersions(infrastructureVersions); + logger.info("Set target version for " + nodeType + " to " + version.toFullString()); + } + } + + public Optional getTargetVersionFor(NodeType nodeType) { + return Optional.ofNullable(db.readInfrastructureVersions().get(nodeType)); + } + + public Map getTargetVersions() { + return Collections.unmodifiableMap(db.readInfrastructureVersions()); + } +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java index 7fc305d397f..44eda5e83ec 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java @@ -2,9 +2,11 @@ package com.yahoo.vespa.hosted.provision.persistence; import com.google.common.util.concurrent.UncheckedTimeoutException; +import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationLockException; import com.yahoo.config.provision.NodeFlavors; +import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.Zone; import com.yahoo.log.LogLevel; import com.yahoo.path.Path; @@ -22,6 +24,7 @@ import java.time.Clock; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -70,6 +73,7 @@ public class CuratorDatabaseClient { for (Node.State state : Node.State.values()) curatorDatabase.create(toPath(state)); curatorDatabase.create(inactiveJobsPath()); + curatorDatabase.create(infrastructureVersionsPath()); } /** @@ -363,5 +367,27 @@ public class CuratorDatabaseClient { private Path inactiveJobsPath() { return root.append("inactiveJobs"); } - + + + public Map readInfrastructureVersions() { + byte[] data = curatorDatabase.getData(infrastructureVersionsPath()).get(); + if (data.length == 0) return new HashMap<>(); // infrastructure versions have never been written + return InfrastructureVersionsSerializer.fromJson(data); + } + + public void writeInfrastructureVersions(Map infrastructureVersions) { + NestedTransaction transaction = new NestedTransaction(); + CuratorTransaction curatorTransaction = curatorDatabase.newCuratorTransactionIn(transaction); + curatorTransaction.add(CuratorOperations.setData(infrastructureVersionsPath().getAbsolute(), + InfrastructureVersionsSerializer.toJson(infrastructureVersions))); + transaction.commit(); + } + + public Lock lockInfrastructureVersions() { + return lock(root.append("locks").append("infrastructureVersionsLock"), defaultLockTimeout); + } + + private Path infrastructureVersionsPath() { + return root.append("infrastructureVersions"); + } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/InfrastructureVersionsSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/InfrastructureVersionsSerializer.java new file mode 100644 index 00000000000..a48888fb4f0 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/InfrastructureVersionsSerializer.java @@ -0,0 +1,42 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.persistence; + +import com.yahoo.component.Version; +import com.yahoo.config.provision.NodeType; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Inspector; +import com.yahoo.slime.ObjectTraverser; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.config.SlimeUtils; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author freva + */ +class InfrastructureVersionsSerializer { + + private InfrastructureVersionsSerializer() {} + + static byte[] toJson(Map versionsByNodeType) { + try { + Slime slime = new Slime(); + Cursor object = slime.setObject(); + versionsByNodeType.forEach((nodeType, version) -> + object.setString(NodeSerializer.toString(nodeType), version.toFullString())); + return SlimeUtils.toJsonBytes(slime); + } catch (IOException e) { + throw new RuntimeException("Serialization of a infrastructure version failed", e); + } + } + + static Map fromJson(byte[] data) { + Map infrastructureVersions = new HashMap<>(); + Inspector inspector = SlimeUtils.jsonToSlime(data).get(); + inspector.traverse((ObjectTraverser) (key, value) -> + infrastructureVersions.put(NodeSerializer.nodeTypeFromString(key), Version.fromString(value.asString()))); + return infrastructureVersions; + } +} 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 d164252b018..4d753599c4e 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 @@ -317,7 +317,7 @@ public class NodeSerializer { throw new IllegalArgumentException("Serialized form of '" + agent + "' not defined"); } - private NodeType nodeTypeFromString(String typeString) { + static NodeType nodeTypeFromString(String typeString) { switch (typeString) { case "tenant" : return NodeType.tenant; case "host" : return NodeType.host; @@ -329,7 +329,7 @@ public class NodeSerializer { } } - private String toString(NodeType type) { + static String toString(NodeType type) { switch (type) { case tenant: return "tenant"; case host: return "host"; diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersionsTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersionsTest.java new file mode 100644 index 00000000000..37b0097b17c --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersionsTest.java @@ -0,0 +1,83 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.maintenance; + +import com.yahoo.component.Version; +import com.yahoo.config.provision.NodeType; +import com.yahoo.vespa.hosted.provision.NodeRepositoryTester; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static junit.framework.TestCase.fail; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author freva + */ +public class InfrastructureVersionsTest { + + private final NodeRepositoryTester tester = new NodeRepositoryTester(); + private final InfrastructureVersions infrastructureVersions = + new InfrastructureVersions(tester.nodeRepository().database()); + + private final Version version = Version.fromString("6.123.456"); + + @Test + public void can_only_downgrade_with_force() { + assertTrue(infrastructureVersions.getTargetVersions().isEmpty()); + + assertEquals(Optional.empty(), infrastructureVersions.getTargetVersionFor(NodeType.config)); + infrastructureVersions.setTargetVersion(NodeType.config, version, false); + assertEquals(Optional.of(version), infrastructureVersions.getTargetVersionFor(NodeType.config)); + + // Upgrading to new version without force is fine + Version new_version = Version.fromString("6.123.457"); // version + 1 + infrastructureVersions.setTargetVersion(NodeType.config, new_version, false); + assertEquals(Optional.of(new_version), infrastructureVersions.getTargetVersionFor(NodeType.config)); + + // Downgrading to old version without force fails + try { + infrastructureVersions.setTargetVersion(NodeType.config, version, false); + fail("Should not be able to downgrade without force"); + } catch (IllegalArgumentException ignored) { } + + infrastructureVersions.setTargetVersion(NodeType.config, version, true); + assertEquals(Optional.of(version), infrastructureVersions.getTargetVersionFor(NodeType.config)); + } + + @Test + public void can_only_set_version_on_certain_node_types() { + // We can set version for config + infrastructureVersions.setTargetVersion(NodeType.config, version, false); + + try { + infrastructureVersions.setTargetVersion(NodeType.tenant, version, false); + fail("Should not be able to set version for tenant nodes"); + } catch (IllegalArgumentException ignored) { } + + try { + // Using 'force' does not help, force only applies to version downgrade + infrastructureVersions.setTargetVersion(NodeType.tenant, version, true); + fail("Should not be able to set version for tenant nodes"); + } catch (IllegalArgumentException ignored) { } + } + + @Test + public void can_store_multiple_versions() { + Version version2 = Version.fromString("6.456.123"); + + infrastructureVersions.setTargetVersion(NodeType.config, version, false); + infrastructureVersions.setTargetVersion(NodeType.confighost, version2, false); + infrastructureVersions.setTargetVersion(NodeType.proxyhost, version, false); + + Map expected = new HashMap<>(); + expected.put(NodeType.config, version); + expected.put(NodeType.confighost, version2); + expected.put(NodeType.proxyhost, version); + + assertEquals(expected, infrastructureVersions.getTargetVersions()); + } +} \ No newline at end of file -- cgit v1.2.3 From adfbf772fc345ef7559832f8c07b6323490190d0 Mon Sep 17 00:00:00 2001 From: Valerij Fredriksen Date: Wed, 2 May 2018 14:42:32 +0200 Subject: Add REST API to update/view infrastructure versions --- .../maintenance/NodeRepositoryMaintenance.java | 8 ++++- .../provision/restapi/v2/NodesApiHandler.java | 32 ++++++++++++++--- .../provision/restapi/v2/UpgradeResponse.java | 42 ++++++++++++++++++++++ .../hosted/provision/restapi/v2/RestApiTest.java | 38 ++++++++++++++++++++ .../restapi/v2/responses/maintenance.json | 3 ++ .../provision/restapi/v2/responses/root.json | 3 ++ 6 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/UpgradeResponse.java (limited to 'node-repository') diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java index 18af498a38d..7e8cd65d37f 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java @@ -48,10 +48,11 @@ public class NodeRepositoryMaintenance extends AbstractComponent { private final MetricsReporter metricsReporter; private final JobControl jobControl; + private final InfrastructureVersions infrastructureVersions; @Inject public NodeRepositoryMaintenance(NodeRepository nodeRepository, Deployer deployer, - HostLivenessTracker hostLivenessTracker, ServiceMonitor serviceMonitor, + HostLivenessTracker hostLivenessTracker, ServiceMonitor serviceMonitor, Zone zone, Orchestrator orchestrator, Metric metric, ConfigserverConfig configserverConfig) { this(nodeRepository, deployer, hostLivenessTracker, serviceMonitor, zone, Clock.systemUTC(), @@ -64,6 +65,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent { ConfigserverConfig configserverConfig) { DefaultTimes defaults = new DefaultTimes(zone.environment()); jobControl = new JobControl(nodeRepository.database()); + infrastructureVersions = new InfrastructureVersions(nodeRepository.database()); nodeFailer = new NodeFailer(deployer, hostLivenessTracker, serviceMonitor, nodeRepository, durationFromEnv("fail_grace").orElse(defaults.failGrace), clock, orchestrator, throttlePolicyFromEnv("throttle_policy").orElse(defaults.throttlePolicy), metric, jobControl, configserverConfig); periodicApplicationMaintainer = new PeriodicApplicationMaintainer(deployer, nodeRepository, durationFromEnv("periodic_redeploy_interval").orElse(defaults.periodicRedeployInterval), jobControl); @@ -101,6 +103,10 @@ public class NodeRepositoryMaintenance extends AbstractComponent { public JobControl jobControl() { return jobControl; } + public InfrastructureVersions infrastructureVersions() { + return infrastructureVersions; + } + private static Optional durationFromEnv(String envVariable) { return Optional.ofNullable(System.getenv(envPrefix + envVariable)).map(Long::parseLong).map(Duration::ofSeconds); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java index 03777078251..54202a15971 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.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.hosted.provision.restapi.v2; +import com.yahoo.component.Version; import com.yahoo.config.provision.HostFilter; import com.yahoo.config.provision.NodeType; import com.yahoo.container.jdisc.HttpRequest; @@ -91,7 +92,7 @@ public class NodesApiHandler extends LoggingRequestHandler { private HttpResponse handleGET(HttpRequest request) { String path = request.getUri().getPath(); - if (path.equals( "/nodes/v2/")) return ResourcesResponse.fromStrings(request.getUri(), "state", "node", "command", "maintenance"); + if (path.equals( "/nodes/v2/")) return ResourcesResponse.fromStrings(request.getUri(), "state", "node", "command", "maintenance", "upgrade"); if (path.equals( "/nodes/v2/node/")) return new NodesResponse(ResponseType.nodeList, request, orchestrator, nodeRepository); if (path.startsWith("/nodes/v2/node/")) return new NodesResponse(ResponseType.singleNode, request, orchestrator, nodeRepository); if (path.equals( "/nodes/v2/state/")) return new NodesResponse(ResponseType.stateList, request, orchestrator, nodeRepository); @@ -99,6 +100,7 @@ public class NodesApiHandler extends LoggingRequestHandler { if (path.startsWith("/nodes/v2/acl/")) return new NodeAclResponse(request, nodeRepository); if (path.equals( "/nodes/v2/command/")) return ResourcesResponse.fromStrings(request.getUri(), "restart", "reboot"); if (path.equals( "/nodes/v2/maintenance/")) return new JobsResponse(maintenance.jobControl()); + if (path.equals( "/nodes/v2/upgrade/")) return new UpgradeResponse(maintenance.infrastructureVersions()); throw new NotFoundException("Nothing at path '" + path + "'"); } @@ -135,10 +137,16 @@ public class NodesApiHandler extends LoggingRequestHandler { private HttpResponse handlePATCH(HttpRequest request) { String path = request.getUri().getPath(); - if ( ! path.startsWith("/nodes/v2/node/")) throw new NotFoundException("Nothing at '" + path + "'"); - Node node = nodeFromRequest(request); - nodeRepository.write(new NodePatcher(nodeFlavors, request.getData(), node, nodeRepository).apply()); - return new MessageResponse("Updated " + node.hostname()); + if (path.startsWith("/nodes/v2/node/")) { + Node node = nodeFromRequest(request); + nodeRepository.write(new NodePatcher(nodeFlavors, request.getData(), node, nodeRepository).apply()); + return new MessageResponse("Updated " + node.hostname()); + } + else if (path.startsWith("/nodes/v2/upgrade/")) { + return setInfrastructureVersion(request); + } + + throw new NotFoundException("Nothing at '" + path + "'"); } private HttpResponse handlePOST(HttpRequest request) { @@ -276,4 +284,18 @@ public class NodesApiHandler extends LoggingRequestHandler { return new MessageResponse((active ? "Re-activated" : "Deactivated" ) + " job '" + jobName + "'"); } + private MessageResponse setInfrastructureVersion(HttpRequest request) { + NodeType nodeType = NodeType.valueOf(lastElement(request.getUri().getPath()).toLowerCase()); + Inspector inspector = toSlime(request.getData()).get(); + + Inspector versionField = inspector.field("version"); + if (!versionField.valid()) + throw new IllegalArgumentException("'version' is missing"); + Version version = Version.fromString(versionField.asString()); + boolean force = inspector.field("force").asBool(); + + maintenance.infrastructureVersions().setTargetVersion(nodeType, version, force); + + return new MessageResponse("Set version for " + nodeType + " to " + version.toFullString()); + } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/UpgradeResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/UpgradeResponse.java new file mode 100644 index 00000000000..12d4e935231 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/UpgradeResponse.java @@ -0,0 +1,42 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.restapi.v2; + +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.JsonFormat; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.hosted.provision.maintenance.InfrastructureVersions; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Comparator; +import java.util.Map; + +/** A response containing infrastructure versions */ +public class UpgradeResponse extends HttpResponse { + + private final InfrastructureVersions infrastructureVersions; + + public UpgradeResponse(InfrastructureVersions infrastructureVersions) { + super(200); + this.infrastructureVersions = infrastructureVersions; + } + + @Override + public void render(OutputStream stream) throws IOException { + Slime slime = new Slime(); + Cursor root = slime.setObject(); + + Cursor versionsObject = root.setObject("versions"); + infrastructureVersions.getTargetVersions().entrySet().stream() + .sorted(Comparator.comparing(Map.Entry::getKey)) // Sort for stable tests + .forEach(entry -> + versionsObject.setString(entry.getKey().name(), entry.getValue().toFullString())); + + new JsonFormat(true).encode(stream, slime); + } + + @Override + public String getContentType() { return "application/json"; } + +} 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 2e69867e9b1..5e58d0d89e7 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 @@ -495,6 +495,44 @@ public class RestApiTest { assertFile(new Request("http://localhost:8080/nodes/v2/node/host6.yahoo.com"), "node6.json"); } + @Test + public void test_upgrade() throws IOException { + // Initially, no versions are set + assertResponse(new Request("http://localhost:8080/nodes/v2/upgrade/"), "{\"versions\":{}}"); + + // Set version for config and confighost + assertResponse(new Request("http://localhost:8080/nodes/v2/upgrade/config", + Utf8.toBytes("{\"version\": \"6.123.456\"}"), + Request.Method.PATCH), + "{\"message\":\"Set version for config to 6.123.456\"}"); + assertResponse(new Request("http://localhost:8080/nodes/v2/upgrade/confighost", + Utf8.toBytes("{\"version\": \"6.123.456\"}"), + Request.Method.PATCH), + "{\"message\":\"Set version for confighost to 6.123.456\"}"); + + // Verify versions are set + assertResponse(new Request("http://localhost:8080/nodes/v2/upgrade/"), + "{\"versions\":{\"config\":\"6.123.456\",\"confighost\":\"6.123.456\"}}"); + + // Downgrade without force fails + assertResponse(new Request("http://localhost:8080/nodes/v2/upgrade/confighost", + Utf8.toBytes("{\"version\": \"6.123.1\"}"), + Request.Method.PATCH), + 400, + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Cannot downgrade version without setting 'force'. " + + "Current wanted version: 6.123.456, attempted to set wanted version: 6.123.1\"}"); + + // Downgrade with force is OK + assertResponse(new Request("http://localhost:8080/nodes/v2/upgrade/confighost", + Utf8.toBytes("{\"version\": \"6.123.1\",\"force\":true}"), + Request.Method.PATCH), + "{\"message\":\"Set version for confighost to 6.123.1\"}"); + + // Verify version has been updated + assertResponse(new Request("http://localhost:8080/nodes/v2/upgrade/"), + "{\"versions\":{\"config\":\"6.123.456\",\"confighost\":\"6.123.1\"}}"); + } + /** 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/maintenance.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json index 28e28f9678e..99cb9fd91f5 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/maintenance.json @@ -33,6 +33,9 @@ { "name":"MetricsReporter" }, + { + "name":"InfrastructureProvisioner" + }, { "name":"NodeFailer" } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/root.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/root.json index 4224718ab06..86becefb146 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/root.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/v2/responses/root.json @@ -11,6 +11,9 @@ }, { "url": "http://localhost:8080/nodes/v2/maintenance/" + }, + { + "url":"http://localhost:8080/nodes/v2/upgrade/" } ] } \ No newline at end of file -- cgit v1.2.3 From cb862f27cc73b8a787fb0399e76cbb6041ee5d29 Mon Sep 17 00:00:00 2001 From: Valerij Fredriksen Date: Thu, 3 May 2018 23:41:16 +0200 Subject: Add InfrastructureProvisioner --- .../maintenance/InfrastructureProvisioner.java | 99 +++++++++++++++++++ .../maintenance/NodeRepositoryMaintenance.java | 13 ++- .../provision/testutils/ContainerConfig.java | 1 + .../provision/testutils/MockProvisioner.java | 42 ++++++++ .../maintenance/InfrastructureProvisionerTest.java | 110 +++++++++++++++++++++ 5 files changed, 262 insertions(+), 3 deletions(-) create mode 100644 node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java create mode 100644 node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java create mode 100644 node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java (limited to 'node-repository') diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java new file mode 100644 index 00000000000..c958be13a5d --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java @@ -0,0 +1,99 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.maintenance; + +import com.yahoo.component.Version; +import com.yahoo.config.provision.HostSpec; +import com.yahoo.config.provision.NodeType; +import com.yahoo.config.provision.Provisioner; +import com.yahoo.log.LogLevel; +import com.yahoo.transaction.Mutex; +import com.yahoo.transaction.NestedTransaction; +import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.service.monitor.application.ConfigServerApplication; +import com.yahoo.vespa.service.monitor.application.ConfigServerHostApplication; +import com.yahoo.vespa.service.monitor.application.HostedVespaApplication; +import com.yahoo.vespa.service.monitor.application.ProxyHostApplication; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * @author freva + */ +public class InfrastructureProvisioner extends Maintainer { + + private static final Logger logger = Logger.getLogger(InfrastructureProvisioner.class.getName()); + private static final List HOSTED_VESPA_APPLICATIONS = Arrays.asList( + ConfigServerApplication.CONFIG_SERVER_APPLICATION, + ConfigServerHostApplication.CONFIG_SERVER_HOST_APPLICATION, + ProxyHostApplication.PROXY_HOST_APPLICATION); + + private final Provisioner provisioner; + private final InfrastructureVersions infrastructureVersions; + + public InfrastructureProvisioner(Provisioner provisioner, NodeRepository nodeRepository, + InfrastructureVersions infrastructureVersions, Duration interval, JobControl jobControl) { + super(nodeRepository, interval, jobControl); + this.provisioner = provisioner; + this.infrastructureVersions = infrastructureVersions; + } + + @Override + protected void maintain() { + for (HostedVespaApplication application: HOSTED_VESPA_APPLICATIONS) { + try (Mutex lock = nodeRepository().lock(application.getApplicationId())) { + Optional version = getVersionToProvision(application.getCapacity().type()); + if (! version.isPresent()) continue; + + List hostSpecs = provisioner.prepare( + application.getApplicationId(), + application.getClusterSpecWithVersion(version.get()), + application.getCapacity(), + 1, // groups + logger::log); + + NestedTransaction nestedTransaction = new NestedTransaction(); + provisioner.activate(nestedTransaction, application.getApplicationId(), hostSpecs); + nestedTransaction.commit(); + } + } + } + + /** + * Returns the version that the given node type should be provisioned to. This is + * the version returned by {@link InfrastructureVersions#getTargetVersionFor} unless a provisioning is: + *
    + *
  • not possible: no nodes of given type in legal state in node-repo
  • + *
  • redudant: all nodes that can be provisioned already have the right wanted Vespa version
  • + *
+ */ + Optional getVersionToProvision(NodeType nodeType) { + Optional wantedWantedVersion = infrastructureVersions.getTargetVersionFor(nodeType); + if (!wantedWantedVersion.isPresent()) { + logger.log(LogLevel.DEBUG, "Skipping provision of " + nodeType + ": No target version set"); + return Optional.empty(); + } + + List> currentWantedVersions = nodeRepository().getNodes(nodeType, + Node.State.ready, Node.State.reserved, Node.State.active, Node.State.inactive).stream() + .map(node -> node.allocation() + .map(allocation -> allocation.membership().cluster().vespaVersion())) + .collect(Collectors.toList()); + if (currentWantedVersions.isEmpty()) { + logger.log(LogLevel.DEBUG, "Skipping provision of " + nodeType + ": No nodes to provision"); + return Optional.empty(); + } + + if (currentWantedVersions.stream().allMatch(wantedWantedVersion::equals)) { + logger.log(LogLevel.DEBUG, "Skipping provision of " + nodeType + + ": Already provisioned to wanted version " + wantedWantedVersion); + return Optional.empty(); + } + return wantedWantedVersion; + } +} diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java index 7e8cd65d37f..32c7a4035d9 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintenance.java @@ -7,6 +7,7 @@ import com.yahoo.component.AbstractComponent; import com.yahoo.config.provision.Deployer; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.HostLivenessTracker; +import com.yahoo.config.provision.Provisioner; import com.yahoo.config.provision.Zone; import com.yahoo.jdisc.Metric; import com.yahoo.vespa.hosted.provision.NodeRepository; @@ -46,20 +47,21 @@ public class NodeRepositoryMaintenance extends AbstractComponent { private final NodeRebooter nodeRebooter; private final NodeRetirer nodeRetirer; private final MetricsReporter metricsReporter; + private final InfrastructureProvisioner infrastructureProvisioner; private final JobControl jobControl; private final InfrastructureVersions infrastructureVersions; @Inject - public NodeRepositoryMaintenance(NodeRepository nodeRepository, Deployer deployer, + public NodeRepositoryMaintenance(NodeRepository nodeRepository, Deployer deployer, Provisioner provisioner, HostLivenessTracker hostLivenessTracker, ServiceMonitor serviceMonitor, Zone zone, Orchestrator orchestrator, Metric metric, ConfigserverConfig configserverConfig) { - this(nodeRepository, deployer, hostLivenessTracker, serviceMonitor, zone, Clock.systemUTC(), + this(nodeRepository, deployer, provisioner, hostLivenessTracker, serviceMonitor, zone, Clock.systemUTC(), orchestrator, metric, configserverConfig); } - public NodeRepositoryMaintenance(NodeRepository nodeRepository, Deployer deployer, + public NodeRepositoryMaintenance(NodeRepository nodeRepository, Deployer deployer, Provisioner provisioner, HostLivenessTracker hostLivenessTracker, ServiceMonitor serviceMonitor, Zone zone, Clock clock, Orchestrator orchestrator, Metric metric, ConfigserverConfig configserverConfig) { @@ -78,6 +80,8 @@ public class NodeRepositoryMaintenance extends AbstractComponent { provisionedExpirer = new ProvisionedExpirer(nodeRepository, clock, durationFromEnv("provisioned_expiry").orElse(defaults.provisionedExpiry), jobControl); nodeRebooter = new NodeRebooter(nodeRepository, clock, durationFromEnv("reboot_interval").orElse(defaults.rebootInterval), jobControl); metricsReporter = new MetricsReporter(nodeRepository, metric, orchestrator, serviceMonitor, durationFromEnv("metrics_interval").orElse(defaults.metricsInterval), jobControl); + infrastructureProvisioner = new InfrastructureProvisioner(provisioner, nodeRepository, infrastructureVersions, durationFromEnv("infrastructure_provision_interval").orElse(defaults.infrastructureProvisionInterval), jobControl); + RetirementPolicy policy = new RetirementPolicyList(new RetireIPv4OnlyNodes(zone)); FlavorSpareChecker flavorSpareChecker = new FlavorSpareChecker( @@ -99,6 +103,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent { nodeRetirer.deconstruct(); provisionedExpirer.deconstruct(); metricsReporter.deconstruct(); + infrastructureProvisioner.deconstruct(); } public JobControl jobControl() { return jobControl; } @@ -142,6 +147,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent { private final Duration nodeRetirerInterval; private final Duration metricsInterval; private final Duration retiredInterval; + private final Duration infrastructureProvisionInterval; private final NodeFailer.ThrottlePolicy throttlePolicy; @@ -154,6 +160,7 @@ public class NodeRepositoryMaintenance extends AbstractComponent { rebootInterval = Duration.ofDays(30); nodeRetirerInterval = Duration.ofMinutes(30); metricsInterval = Duration.ofMinutes(1); + infrastructureProvisionInterval = Duration.ofMinutes(3); throttlePolicy = NodeFailer.ThrottlePolicy.hosted; if (environment.isTest()) diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java index c5067c0f959..6c43ed18645 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/ContainerConfig.java @@ -16,6 +16,7 @@ public class ContainerConfig { " " + " " + " " + + " " + " " + " " + " " + diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java new file mode 100644 index 00000000000..44ee9390e65 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java @@ -0,0 +1,42 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.testutils; + +import com.google.inject.Inject; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.HostFilter; +import com.yahoo.config.provision.HostSpec; +import com.yahoo.config.provision.ProvisionLogger; +import com.yahoo.config.provision.Provisioner; +import com.yahoo.transaction.NestedTransaction; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class MockProvisioner implements Provisioner { + + @Inject + public MockProvisioner() {} + + @Override + public List prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) { + return Collections.emptyList(); + } + + @Override + public void activate(NestedTransaction transaction, ApplicationId application, Collection hosts) { + + } + + @Override + public void remove(NestedTransaction transaction, ApplicationId application) { + + } + + @Override + public void restart(ApplicationId application, HostFilter filter) { + + } +} diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java new file mode 100644 index 00000000000..56c7114064b --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java @@ -0,0 +1,110 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.maintenance; + +import com.yahoo.component.Version; +import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.ClusterMembership; +import com.yahoo.config.provision.NodeType; +import com.yahoo.config.provision.Provisioner; +import com.yahoo.vespa.hosted.provision.Node; +import com.yahoo.vespa.hosted.provision.NodeRepository; +import com.yahoo.vespa.hosted.provision.NodeRepositoryTester; +import com.yahoo.vespa.hosted.provision.node.Agent; +import com.yahoo.vespa.hosted.provision.node.Allocation; +import com.yahoo.vespa.hosted.provision.node.Generation; +import com.yahoo.vespa.service.monitor.application.ConfigServerApplication; + +import java.time.Duration; +import java.util.Optional; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author freva + */ +public class InfrastructureProvisionerTest { + + private final NodeRepositoryTester tester = new NodeRepositoryTester(); + + private final Provisioner provisioner = mock(Provisioner.class); + private final NodeRepository nodeRepository = tester.nodeRepository(); + private final InfrastructureVersions infrastructureVersions = mock(InfrastructureVersions.class); + private final InfrastructureProvisioner infrastructureProvisioner = new InfrastructureProvisioner( + provisioner, nodeRepository, infrastructureVersions, Duration.ofDays(99), new JobControl(nodeRepository.database())); + + @Test + public void returns_version_if_usable_nodes_on_old_version() { + Version target = Version.fromString("6.123.456"); + Version oldVersion = Version.fromString("6.122.333"); + when(infrastructureVersions.getTargetVersionFor(eq(NodeType.config))).thenReturn(Optional.of(target)); + + addNode(1, Node.State.failed, Optional.of(oldVersion)); + addNode(2, Node.State.dirty, Optional.empty()); + addNode(3, Node.State.active, Optional.of(oldVersion)); + + assertEquals(Optional.of(target), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + } + + @Test + public void returns_version_if_has_usable_nodes_without_version() { + Version target = Version.fromString("6.123.456"); + Version oldVersion = Version.fromString("6.122.333"); + when(infrastructureVersions.getTargetVersionFor(eq(NodeType.config))).thenReturn(Optional.of(target)); + + addNode(1, Node.State.failed, Optional.of(oldVersion)); + addNode(2, Node.State.ready, Optional.empty()); + addNode(3, Node.State.active, Optional.of(target)); + + assertEquals(Optional.of(target), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + } + + @Test + public void returns_empty_if_usable_nodes_on_target_version() { + Version target = Version.fromString("6.123.456"); + Version oldVersion = Version.fromString("6.122.333"); + when(infrastructureVersions.getTargetVersionFor(eq(NodeType.config))).thenReturn(Optional.of(target)); + + addNode(1, Node.State.failed, Optional.of(oldVersion)); + addNode(2, Node.State.parked, Optional.of(target)); + addNode(3, Node.State.active, Optional.of(target)); + addNode(4, Node.State.inactive, Optional.of(target)); + addNode(5, Node.State.dirty, Optional.empty()); + + assertEquals(Optional.empty(), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + } + + @Test + public void returns_empty_if_no_usable_nodes() { + when(infrastructureVersions.getTargetVersionFor(eq(NodeType.config))).thenReturn(Optional.of(Version.fromString("6.123.456"))); + + // No nodes in node repo + assertEquals(Optional.empty(), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + + // Add nodes in non-provisionable states + addNode(1, Node.State.dirty, Optional.empty()); + addNode(2, Node.State.failed, Optional.empty()); + assertEquals(Optional.empty(), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + } + + @Test + public void returns_empty_if_target_version_not_set() { + when(infrastructureVersions.getTargetVersionFor(eq(NodeType.config))).thenReturn(Optional.empty()); + assertEquals(Optional.empty(), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + } + + private Node addNode(int id, Node.State state, Optional wantedVespaVersion) { + Node node = tester.addNode("id-" + id, "node-" + id, "default", NodeType.config); + Optional nodeWithAllocation = wantedVespaVersion.map(version -> { + ConfigServerApplication application = ConfigServerApplication.CONFIG_SERVER_APPLICATION; + ClusterSpec clusterSpec = ClusterSpec.from(application.getClusterType(), application.getClusterId(), ClusterSpec.Group.from(0), version); + ClusterMembership membership = ClusterMembership.from(clusterSpec, 1); + Allocation allocation = new Allocation(application.getApplicationId(), membership, new Generation(0, 0), false); + return node.with(allocation); + }); + return nodeRepository.database().writeTo(state, nodeWithAllocation.orElse(node), Agent.system, Optional.empty()); + } +} -- cgit v1.2.3 From 05e338db4a96e7b195f39bb7a35782ac8c4ece38 Mon Sep 17 00:00:00 2001 From: Valerij Fredriksen Date: Fri, 4 May 2018 14:10:31 +0200 Subject: Code review fixes --- .../maintenance/InfrastructureProvisioner.java | 27 +++++++++++++--------- .../maintenance/InfrastructureVersions.java | 20 ++++++++-------- .../provision/restapi/v2/UpgradeResponse.java | 6 ++++- .../provision/testutils/MockProvisioner.java | 5 +++- .../maintenance/InfrastructureProvisionerTest.java | 12 +++++----- .../hosted/provision/restapi/v2/RestApiTest.java | 2 +- 6 files changed, 42 insertions(+), 30 deletions(-) (limited to 'node-repository') diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java index c958be13a5d..b6955195dcf 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisioner.java @@ -23,6 +23,10 @@ import java.util.logging.Logger; import java.util.stream.Collectors; /** + * This maintainer makes sure that infrastructure nodes are allocated with correct wanted + * version. Source for the wanted version comes from the target version set using + * /nodes/v2/upgrade/ endpoint. + * * @author freva */ public class InfrastructureProvisioner extends Maintainer { @@ -47,7 +51,7 @@ public class InfrastructureProvisioner extends Maintainer { protected void maintain() { for (HostedVespaApplication application: HOSTED_VESPA_APPLICATIONS) { try (Mutex lock = nodeRepository().lock(application.getApplicationId())) { - Optional version = getVersionToProvision(application.getCapacity().type()); + Optional version = getTargetVersion(application.getCapacity().type()); if (! version.isPresent()) continue; List hostSpecs = provisioner.prepare( @@ -69,31 +73,32 @@ public class InfrastructureProvisioner extends Maintainer { * the version returned by {@link InfrastructureVersions#getTargetVersionFor} unless a provisioning is: *
    *
  • not possible: no nodes of given type in legal state in node-repo
  • - *
  • redudant: all nodes that can be provisioned already have the right wanted Vespa version
  • + *
  • redundant: all nodes that can be provisioned already have the right wanted Vespa version
  • *
*/ - Optional getVersionToProvision(NodeType nodeType) { - Optional wantedWantedVersion = infrastructureVersions.getTargetVersionFor(nodeType); - if (!wantedWantedVersion.isPresent()) { + Optional getTargetVersion(NodeType nodeType) { + Optional targetVersion = infrastructureVersions.getTargetVersionFor(nodeType); + if (!targetVersion.isPresent()) { logger.log(LogLevel.DEBUG, "Skipping provision of " + nodeType + ": No target version set"); return Optional.empty(); } - List> currentWantedVersions = nodeRepository().getNodes(nodeType, + List wantedVersions = nodeRepository().getNodes(nodeType, Node.State.ready, Node.State.reserved, Node.State.active, Node.State.inactive).stream() .map(node -> node.allocation() - .map(allocation -> allocation.membership().cluster().vespaVersion())) + .map(allocation -> allocation.membership().cluster().vespaVersion()) + .orElse(null)) .collect(Collectors.toList()); - if (currentWantedVersions.isEmpty()) { + if (wantedVersions.isEmpty()) { logger.log(LogLevel.DEBUG, "Skipping provision of " + nodeType + ": No nodes to provision"); return Optional.empty(); } - if (currentWantedVersions.stream().allMatch(wantedWantedVersion::equals)) { + if (wantedVersions.stream().allMatch(targetVersion.get()::equals)) { logger.log(LogLevel.DEBUG, "Skipping provision of " + nodeType + - ": Already provisioned to wanted version " + wantedWantedVersion); + ": Already provisioned to target version " + targetVersion); return Optional.empty(); } - return wantedWantedVersion; + return targetVersion; } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersions.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersions.java index b90ec492e1a..52d7a63dc5e 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersions.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureVersions.java @@ -13,7 +13,7 @@ import java.util.logging.Logger; /** * Multithread safe class to see and set target versions for infrastructure node types. * {@link InfrastructureProvisioner} maintainer will then allocate the nodes of given node type - * with a wantedVersionVersion equal to the given target version. + * with a wanted version equal to the given target version. * * @author freva */ @@ -27,29 +27,29 @@ public class InfrastructureVersions { this.db = db; } - public void setTargetVersion(NodeType nodeType, Version version, boolean force) { + public void setTargetVersion(NodeType nodeType, Version newTargetVersion, boolean force) { if (nodeType != NodeType.config && nodeType != NodeType.confighost && nodeType != NodeType.proxyhost) { throw new IllegalArgumentException("Cannot set version for type " + nodeType); } try (Lock lock = db.lockInfrastructureVersions()) { Map infrastructureVersions = db.readInfrastructureVersions(); - Optional currentWantedVersion = Optional.ofNullable(infrastructureVersions.get(nodeType)); + Optional currentTargetVersion = Optional.ofNullable(infrastructureVersions.get(nodeType)); // Trying to set the version to the current version, skip - if (currentWantedVersion.equals(Optional.of(version))) return; + if (currentTargetVersion.equals(Optional.of(newTargetVersion))) return; // If we don't force the set, we must set the new version to higher than the already set version - if (!force) { - if (currentWantedVersion.map(curVersion -> curVersion.compareTo(version) > 0).orElse(false)) + if (!force && currentTargetVersion.isPresent()) { + if (currentTargetVersion.get().isAfter(newTargetVersion)) throw new IllegalArgumentException(String.format("Cannot downgrade version without setting 'force'. " + - "Current wanted version: %s, attempted to set wanted version: %s", - currentWantedVersion.get().toFullString(), version.toFullString())); + "Current target version: %s, attempted to set target version: %s", + currentTargetVersion.get().toFullString(), newTargetVersion.toFullString())); } - infrastructureVersions.put(nodeType, version); + infrastructureVersions.put(nodeType, newTargetVersion); db.writeInfrastructureVersions(infrastructureVersions); - logger.info("Set target version for " + nodeType + " to " + version.toFullString()); + logger.info("Set target version for " + nodeType + " to " + newTargetVersion.toFullString()); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/UpgradeResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/UpgradeResponse.java index 12d4e935231..20a0139a178 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/UpgradeResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/UpgradeResponse.java @@ -12,7 +12,11 @@ import java.io.OutputStream; import java.util.Comparator; import java.util.Map; -/** A response containing infrastructure versions */ +/** + * A response containing infrastructure versions + * + * @author freva + */ public class UpgradeResponse extends HttpResponse { private final InfrastructureVersions infrastructureVersions; diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java index 44ee9390e65..af0ca7b6b75 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.testutils; import com.google.inject.Inject; @@ -15,6 +15,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +/** + * @author freva + */ public class MockProvisioner implements Provisioner { @Inject diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java index 56c7114064b..586498619c6 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InfrastructureProvisionerTest.java @@ -46,7 +46,7 @@ public class InfrastructureProvisionerTest { addNode(2, Node.State.dirty, Optional.empty()); addNode(3, Node.State.active, Optional.of(oldVersion)); - assertEquals(Optional.of(target), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + assertEquals(Optional.of(target), infrastructureProvisioner.getTargetVersion(NodeType.config)); } @Test @@ -59,7 +59,7 @@ public class InfrastructureProvisionerTest { addNode(2, Node.State.ready, Optional.empty()); addNode(3, Node.State.active, Optional.of(target)); - assertEquals(Optional.of(target), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + assertEquals(Optional.of(target), infrastructureProvisioner.getTargetVersion(NodeType.config)); } @Test @@ -74,7 +74,7 @@ public class InfrastructureProvisionerTest { addNode(4, Node.State.inactive, Optional.of(target)); addNode(5, Node.State.dirty, Optional.empty()); - assertEquals(Optional.empty(), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + assertEquals(Optional.empty(), infrastructureProvisioner.getTargetVersion(NodeType.config)); } @Test @@ -82,18 +82,18 @@ public class InfrastructureProvisionerTest { when(infrastructureVersions.getTargetVersionFor(eq(NodeType.config))).thenReturn(Optional.of(Version.fromString("6.123.456"))); // No nodes in node repo - assertEquals(Optional.empty(), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + assertEquals(Optional.empty(), infrastructureProvisioner.getTargetVersion(NodeType.config)); // Add nodes in non-provisionable states addNode(1, Node.State.dirty, Optional.empty()); addNode(2, Node.State.failed, Optional.empty()); - assertEquals(Optional.empty(), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + assertEquals(Optional.empty(), infrastructureProvisioner.getTargetVersion(NodeType.config)); } @Test public void returns_empty_if_target_version_not_set() { when(infrastructureVersions.getTargetVersionFor(eq(NodeType.config))).thenReturn(Optional.empty()); - assertEquals(Optional.empty(), infrastructureProvisioner.getVersionToProvision(NodeType.config)); + assertEquals(Optional.empty(), infrastructureProvisioner.getTargetVersion(NodeType.config)); } private Node addNode(int id, Node.State state, Optional wantedVespaVersion) { 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 5e58d0d89e7..581b82e5fd5 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 @@ -520,7 +520,7 @@ public class RestApiTest { Request.Method.PATCH), 400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Cannot downgrade version without setting 'force'. " + - "Current wanted version: 6.123.456, attempted to set wanted version: 6.123.1\"}"); + "Current target version: 6.123.456, attempted to set target version: 6.123.1\"}"); // Downgrade with force is OK assertResponse(new Request("http://localhost:8080/nodes/v2/upgrade/confighost", -- cgit v1.2.3