diff options
Diffstat (limited to 'controller-server')
9 files changed, 115 insertions, 14 deletions
diff --git a/controller-server/pom.xml b/controller-server/pom.xml index 7e27a727bfd..23d2bfc33d4 100644 --- a/controller-server/pom.xml +++ b/controller-server/pom.xml @@ -8,12 +8,12 @@ <parent> <groupId>com.yahoo.vespa</groupId> <artifactId>parent</artifactId> - <version>6-SNAPSHOT</version> + <version>7-SNAPSHOT</version> <relativePath>../parent/pom.xml</relativePath> </parent> <artifactId>controller-server</artifactId> <packaging>container-plugin</packaging> - <version>6-SNAPSHOT</version> + <version>7-SNAPSHOT</version> <dependencies> diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java index 82355144b20..963dca8d3b5 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java @@ -153,9 +153,8 @@ public class Application { } /** - * Overrides the preferred major version for this application. - * This overrides the major version set in the deployment spec (if any) and the major version the system - * wants to use. + * Overrides the system major version for this application. This override takes effect if the deployment + * spec does not specify a major version. */ public Optional<Integer> majorVersion() { return majorVersion; } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java index 3185b7d5f45..4a8670b9f9e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java @@ -58,6 +58,7 @@ import com.yahoo.vespa.hosted.controller.rotation.RotationLock; import com.yahoo.vespa.hosted.controller.rotation.RotationRepository; import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; import com.yahoo.vespa.hosted.controller.tenant.Tenant; +import com.yahoo.vespa.hosted.controller.versions.VespaVersion; import com.yahoo.vespa.hosted.rotation.config.RotationsConfig; import com.yahoo.yolean.Exceptions; @@ -67,8 +68,10 @@ import java.net.URI; import java.time.Clock; import java.time.Duration; import java.time.Instant; +import java.util.ArrayDeque; import java.util.Collections; import java.util.Comparator; +import java.util.Deque; import java.util.EnumSet; import java.util.HashSet; import java.util.List; @@ -277,7 +280,9 @@ public class ApplicationController { ApplicationVersion applicationVersion; ApplicationPackage applicationPackage; if (canDeployDirectly) { - platformVersion = options.vespaVersion.map(Version::new).orElse(controller.systemVersion()); + platformVersion = options.vespaVersion.map(Version::new).orElse(application.get().deploymentSpec().majorVersion() + .flatMap(this::lastCompatibleVersion) + .orElse(controller.systemVersion())); applicationVersion = applicationVersionFromDeployer.orElse(ApplicationVersion.unknown); applicationPackage = applicationPackageFromDeployer.orElseThrow( () -> new IllegalArgumentException("Application package must be given when deploying to " + zone)); @@ -727,6 +732,14 @@ public class ApplicationController { }); } + /** Returns the latest known version within the given major. */ + private Optional<Version> lastCompatibleVersion(int targetMajorVersion) { + return controller.versionStatus().versions().stream() + .map(VespaVersion::versionNumber) + .filter(version -> version.getMajor() == targetMajorVersion) + .max(naturalOrder()); + } + private boolean isUserDeployment(Optional<AthenzIdentity> identity) { return identity .filter(id -> id instanceof AthenzUser) diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java index 6f1e108ea77..936b0f3fce2 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java @@ -166,7 +166,7 @@ public class ApplicationList { * @param defaultMajorVersion the default major version to assume for applications not specifying one */ public ApplicationList allowMajorVersion(int targetMajorVersion, int defaultMajorVersion) { - return listOf(list.stream().filter(a -> a.majorVersion().orElse(a.deploymentSpec().majorVersion().orElse(defaultMajorVersion)) + return listOf(list.stream().filter(a -> a.deploymentSpec().majorVersion().orElse(a.majorVersion().orElse(defaultMajorVersion)) >= targetMajorVersion)); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java index fa1b8699612..76e15c8de5a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java @@ -2,6 +2,8 @@ package com.yahoo.vespa.hosted.controller.restapi.os; import com.yahoo.component.Version; +import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.RegionName; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.LoggingRequestHandler; @@ -13,7 +15,10 @@ import com.yahoo.slime.Slime; import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.zone.CloudName; +import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; +import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneList; import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse; +import com.yahoo.vespa.hosted.controller.restapi.MessageResponse; import com.yahoo.vespa.hosted.controller.restapi.SlimeJsonResponse; import com.yahoo.vespa.hosted.controller.versions.OsVersion; import com.yahoo.yolean.Exceptions; @@ -21,8 +26,12 @@ import com.yahoo.yolean.Exceptions; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.StringJoiner; import java.util.logging.Level; +import java.util.stream.Collectors; /** * This implements the /os/v1 API which provides operators with information about, and scheduling of OS upgrades for @@ -45,6 +54,8 @@ public class OsApiHandler extends LoggingRequestHandler { try { switch (request.getMethod()) { case GET: return get(request); + case POST: return post(request); + case DELETE: return delete(request); case PATCH: return patch(request); default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is unsupported"); } @@ -68,6 +79,55 @@ public class OsApiHandler extends LoggingRequestHandler { return ErrorResponse.notFoundError("Nothing at " + path); } + private HttpResponse post(HttpRequest request) { + Path path = new Path(request.getUri().getPath()); + if (path.matches("/os/v1/firmware/")) return requestFirmwareCheckResponse(path); + if (path.matches("/os/v1/firmware/{environment}/")) return requestFirmwareCheckResponse(path); + if (path.matches("/os/v1/firmware/{environment}/{region}/")) return requestFirmwareCheckResponse(path); + return ErrorResponse.notFoundError("Nothing at " + path); + } + + private HttpResponse delete(HttpRequest request) { + Path path = new Path(request.getUri().getPath()); + if (path.matches("/os/v1/firmware/")) return cancelFirmwareCheckResponse(path); + if (path.matches("/os/v1/firmware/{environment}/")) return cancelFirmwareCheckResponse(path); + if (path.matches("/os/v1/firmware/{environment}/{region}/")) return cancelFirmwareCheckResponse(path); + return ErrorResponse.notFoundError("Nothing at " + path); + } + + private HttpResponse requestFirmwareCheckResponse(Path path) { + List<ZoneId> zones = zonesAt(path); + if (zones.isEmpty()) + return ErrorResponse.notFoundError("No zones at " + path); + + StringJoiner response = new StringJoiner(", ", "Requested firmware checks in ", "."); + for (ZoneId zone : zones) { + controller.configServer().nodeRepository().requestFirmwareCheck(zone); + response.add(zone.value()); + } + return new MessageResponse(response.toString()); + } + + private HttpResponse cancelFirmwareCheckResponse(Path path) { + List<ZoneId> zones = zonesAt(path); + if (zones.isEmpty()) + return ErrorResponse.notFoundError("No zones at " + path); + + StringJoiner response = new StringJoiner(", ", "Cancelled firmware checks in ", "."); + for (ZoneId zone : zones) { + controller.configServer().nodeRepository().cancelFirmwareCheck(zone); + response.add(zone.value()); + } + return new MessageResponse(response.toString()); + } + + private List<ZoneId> zonesAt(Path path) { + ZoneList zones = controller.zoneRegistry().zones().controllerUpgraded(); + if (path.get("region") != null) zones = zones.in(RegionName.from(path.get("region"))); + if (path.get("environment") != null) zones = zones.in(Environment.from(path.get("environment"))); + return zones.ids(); + } + private Slime setOsVersion(HttpRequest request) { Slime requestData = toSlime(request.getData()); Inspector root = requestData.get(); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java index 81a0a314dc5..d20c86528a5 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java @@ -17,7 +17,6 @@ import java.nio.file.Paths; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.X509Certificate; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -58,12 +57,6 @@ public class ControllerSslContextFactoryProvider extends AbstractComponent imple /** Create a SslContextFactory backed by an in-memory key and trust store */ private SslContextFactory createSslContextFactory(int port) { SslContextFactory factory = new SslContextFactory(); - // TODO Remove cipher exclusions on Vespa 7 (require ciphers with forward secrecy) - // Do not exclude TLS_RSA_* ciphers - String[] excludedCiphers = Arrays.stream(factory.getExcludeCipherSuites()) - .filter(cipherPattern -> !cipherPattern.equals("^TLS_RSA_.*$")) - .toArray(String[]::new); - factory.setExcludeCipherSuites(excludedCiphers); if (port != 443) { factory.setWantClientAuth(true); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java index 1199f0229b6..343deaec752 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java @@ -446,10 +446,13 @@ public class ControllerTest { @Test public void testDeployDirectly() { DeploymentTester tester = new DeploymentTester(); + Version six = Version.fromString("6.1"); + tester.upgradeSystem(six); tester.controllerTester().zoneRegistry().setSystemName(SystemName.cd); tester.controllerTester().zoneRegistry().setZones(ZoneId.from("prod", "cd-us-central-1")); ApplicationPackage applicationPackage = new ApplicationPackageBuilder() .environment(Environment.prod) + .majorVersion(6) .region("cd-us-central-1") .build(); @@ -468,6 +471,11 @@ public class ControllerTest { assertTrue("No job status added", tester.applications().require(app.id()).deploymentJobs().jobStatus().isEmpty()); + + Version seven = Version.fromString("7.2"); + tester.upgradeSystem(seven); + tester.controller().applications().deploy(app.id(), zone, Optional.of(applicationPackage), options); + assertEquals(six, tester.application(app.id()).deployments().get(zone).version()); } @Test diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java index b160b853986..f375d80b28b 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java @@ -12,11 +12,13 @@ import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.function.UnaryOperator; import java.util.stream.Collectors; @@ -93,6 +95,16 @@ public class NodeRepositoryMock implements NodeRepository { .forEach(node -> putByHostname(zone, node)); } + @Override + public void requestFirmwareCheck(ZoneId zone) { + ; + } + + @Override + public void cancelFirmwareCheck(ZoneId zone) { + ; + } + public void doUpgrade(DeploymentId deployment, Optional<HostName> hostName, Version version) { modifyNodes(deployment, hostName, node -> { assert node.wantedVersion().equals(version); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java index a7f98103885..9ec254b59fd 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java @@ -111,6 +111,22 @@ public class OsApiTest extends ControllerContainerTest { assertResponse(new Request("http://localhost:8080/os/v1/", "{\"version\": \"7.4.1\", \"cloud\": \"cloud1\"}", Request.Method.PATCH), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Cannot downgrade cloud 'cloud1' to version 7.4.1\"}", 400); + // Request firmware checks in all zones. + assertResponse(new Request("http://localhost:8080/os/v1/firmware/", "", Request.Method.POST), + "{\"message\":\"Requested firmware checks in prod.us-east-3, prod.us-west-1, prod.eu-west-1.\"}", 200); + + // Cancel firmware checks in all prod zones. + assertResponse(new Request("http://localhost:8080/os/v1/firmware/prod/", "", Request.Method.DELETE), + "{\"message\":\"Cancelled firmware checks in prod.us-east-3, prod.us-west-1, prod.eu-west-1.\"}", 200); + + // Request firmware checks in prod.us-east-3. + assertResponse(new Request("http://localhost:8080/os/v1/firmware/prod/us-east-3", "", Request.Method.POST), + "{\"message\":\"Requested firmware checks in prod.us-east-3.\"}", 200); + + // Error: Cancel firmware checks in an empty set of zones. + assertResponse(new Request("http://localhost:8080/os/v1/firmware/dev/", "", Request.Method.DELETE), + "{\"error-code\":\"NOT_FOUND\",\"message\":\"No zones at path '/os/v1/firmware/dev'\"}", 404); + } private void upgradeAndUpdateStatus() { |