summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java25
-rw-r--r--athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentResource.java12
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/SecretStoreValidator.java38
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/first/AccessControlValidator.java5
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/SecretStoreValidatorTest.java92
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/first/AccessControlValidatorTest.java4
-rw-r--r--eval/CMakeLists.txt1
-rw-r--r--eval/src/tests/tensor/typed_cells/CMakeLists.txt8
-rw-r--r--eval/src/tests/tensor/typed_cells/typed_cells_test.cpp622
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java30
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesApiHandler.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java51
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTester.java11
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/monitoring/MetricsReporterTest.java2
-rw-r--r--parent/pom.xml5
17 files changed, 893 insertions, 22 deletions
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java
index e3a937919fe..8ce13111536 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentGenerator.java
@@ -10,10 +10,12 @@ import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Allocation;
+import java.net.InetAddress;
import java.security.PrivateKey;
import java.security.Signature;
import java.time.Instant;
import java.util.Base64;
+import java.util.Objects;
/**
* @author mortent
@@ -86,5 +88,28 @@ public class IdentityDocumentGenerator {
private static String toZoneDnsSuffix(Zone zone, String dnsSuffix) {
return zone.environment().value() + "-" + zone.region().value() + "." + dnsSuffix;
}
+
+ /*
+ * Basic access control until we have mutual auth where athenz x509certs are distributed on all docker nodes by node admin
+ * Checks:
+ * If remote hostname == requested hostname --> OK
+ * If remote hostname is parent of requested hostname in node repo --> OK
+ * Otherwise NOT OK
+ */
+ boolean validateAccess(String hostname, String remoteAddr) {
+ try {
+ InetAddress addr = InetAddress.getByName(remoteAddr);
+ String remoteHostname = addr.getHostName();
+ if (Objects.equals(hostname, remoteHostname)) {
+ return true;
+ }
+ Node node = nodeRepository.getNode(hostname).orElseThrow(() -> new RuntimeException("Unable to find node " + hostname));
+ return node.parentHostname()
+ .map(parent -> Objects.equals(parent, remoteHostname))
+ .orElse(false);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentResource.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentResource.java
index b3e5aee97b3..a39a7cf1a05 100644
--- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentResource.java
+++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/identitydocument/IdentityDocumentResource.java
@@ -5,12 +5,15 @@ import com.google.inject.Inject;
import com.yahoo.container.jaxrs.annotation.Component;
import com.yahoo.log.LogLevel;
+import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.BadRequestException;
+import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.util.logging.Logger;
@@ -31,8 +34,14 @@ public class IdentityDocumentResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
- public SignedIdentityDocument getIdentityDocument(@QueryParam("hostname") String hostname) {
+ public SignedIdentityDocument getIdentityDocument(@QueryParam("hostname") String hostname,
+ @Context HttpServletRequest request) {
// TODO Use TLS client authentication instead of blindly trusting hostname
+ // Until we have distributed Athenz x509 certificates we will validate that remote address
+ // is authorized to access the provided hostname. This means any container
+ if (!identityDocumentGenerator.validateAccess(hostname, request.getRemoteAddr())) {
+ throw new ForbiddenException();
+ }
if (hostname == null) {
throw new BadRequestException("The 'hostname' query parameter is missing");
}
@@ -44,5 +53,4 @@ public class IdentityDocumentResource {
throw new InternalServerErrorException(message, e);
}
}
-
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/SecretStoreValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/SecretStoreValidator.java
new file mode 100644
index 00000000000..105bc57e770
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/SecretStoreValidator.java
@@ -0,0 +1,38 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.model.ConfigModelContext.ApplicationType;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.vespa.model.VespaModel;
+import com.yahoo.vespa.model.container.ContainerCluster;
+import com.yahoo.vespa.model.container.IdentityProvider;
+import com.yahoo.vespa.model.container.component.Component;
+
+/**
+ * Validates the requirements for setting up a secret store.
+ *
+ * @author gjoranv
+ */
+public class SecretStoreValidator extends Validator {
+
+ @Override
+ public void validate(VespaModel model, DeployState deployState) {
+ if (! deployState.isHosted()) return;
+ if (model.getAdmin().getApplicationType() != ApplicationType.DEFAULT) return;
+
+ for (ContainerCluster cluster : model.getContainerClusters().values()) {
+ if (cluster.getSecretStore().isPresent() && ! hasIdentityProvider(cluster)) {
+ throw new IllegalArgumentException(String.format(
+ "Container cluster '%s' uses a secret store, so an Athenz domain and an Athenz service" +
+ " must be declared in deployment.xml.", cluster.getName()));
+ }
+ }
+ }
+
+ private boolean hasIdentityProvider(ContainerCluster cluster) {
+ for (Component<?, ?> component : cluster.getAllComponents()) {
+ if (component instanceof IdentityProvider) return true;
+ }
+ return false;
+ }
+}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
index f390d6368d1..6407f581a62 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
@@ -55,6 +55,7 @@ public class Validation {
new NoPrefixForIndexes().validate(model, deployState);
new DeploymentFileValidator().validate(model, deployState);
new RankingConstantsValidator().validate(model, deployState);
+ new SecretStoreValidator().validate(model, deployState);
Optional<Model> currentActiveModel = deployState.getPreviousModel();
if (currentActiveModel.isPresent() && (currentActiveModel.get() instanceof VespaModel))
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/first/AccessControlValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/first/AccessControlValidator.java
index d7fb46a8fb2..26a1478d0a7 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/first/AccessControlValidator.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/first/AccessControlValidator.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.model.application.validation.first;
import com.yahoo.config.model.ConfigModelContext.ApplicationType;
import com.yahoo.config.model.deploy.DeployState;
-import com.yahoo.config.provision.SystemName;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.application.validation.Validator;
import com.yahoo.vespa.model.container.ContainerCluster;
@@ -29,10 +28,6 @@ public class AccessControlValidator extends Validator {
if (! deployState.zone().environment().isProduction()) return;
if (model.getAdmin().getApplicationType() != ApplicationType.DEFAULT) return;
- // Temporarily validate apps in CD zones only
- // TODO: remove, and also remove the zone setting in the unit test
- if (deployState.zone().system() != SystemName.cd) return;
-
List<String> offendingClusters = new ArrayList<>();
for (ContainerCluster cluster : model.getContainerClusters().values()) {
if (cluster.getHttp() == null
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/SecretStoreValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/SecretStoreValidatorTest.java
new file mode 100644
index 00000000000..cac3e65de89
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/SecretStoreValidatorTest.java
@@ -0,0 +1,92 @@
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.deploy.DeployProperties;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static com.yahoo.config.model.test.TestUtil.joinLines;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author gjoranv
+ */
+public class SecretStoreValidatorTest {
+ @Rule
+ public final ExpectedException exceptionRule = ExpectedException.none();
+
+ private static String servicesXml() {
+ return joinLines("<services version='1.0'>",
+ " <container id='default' version='1.0'>",
+ " <secret-store>",
+ " <group name='group1' environment='prod'/>",
+ " </secret-store>",
+ " </container>",
+ "</services>");
+ }
+
+ private static String deploymentXml(boolean addAthenz) {
+ return joinLines("<deployment version='1.0' " + (addAthenz ?
+ "athenz-domain='domain' athenz-service='service'" : "") + ">",
+ " <prod />",
+ "</deployment>");
+ }
+
+ @Test
+ public void app_with_athenz_in_deployment_passes_validation() throws Exception {
+ DeployState deployState = deployState(servicesXml(), deploymentXml(true));
+ VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState);
+
+ new SecretStoreValidator().validate(model, deployState);
+ }
+
+ @Test
+ public void app_without_athenz_in_deployment_fails_validation() throws Exception {
+ exceptionRule.expect(IllegalArgumentException.class);
+ exceptionRule.expectMessage(
+ "Container cluster 'default' uses a secret store, so an Athenz domain and" +
+ " an Athenz service must be declared in deployment.xml.");
+
+ DeployState deployState = deployState(servicesXml(), deploymentXml(false));
+ VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState);
+
+ new SecretStoreValidator().validate(model, deployState);
+
+ }
+
+ @Test
+ public void app_without_secret_store_passes_validation_without_athenz_in_deployment() throws Exception {
+ String servicesXml = joinLines("<services version='1.0'>",
+ " <container id='default' version='1.0' />",
+ "</services>");
+ DeployState deployState = deployState(servicesXml, deploymentXml(false));
+ VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState);
+
+ new SecretStoreValidator().validate(model, deployState);
+ }
+
+ private static DeployState deployState(String servicesXml, String deploymentXml) {
+ ApplicationPackage app = new MockApplicationPackage.Builder()
+ .withServices(servicesXml)
+ .withDeploymentSpec(deploymentXml)
+ .build();
+ DeployState.Builder builder = new DeployState.Builder()
+ .applicationPackage(app)
+ .zone(new Zone(Environment.prod, RegionName.from("foo")))
+ .properties(new DeployProperties.Builder()
+ .hostedVespa(true)
+ .build());
+ final DeployState deployState = builder.build(true);
+
+ assertTrue("Test must emulate a hosted deployment.", deployState.isHosted());
+ return deployState;
+ }
+}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/first/AccessControlValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/first/AccessControlValidatorTest.java
index 82d521516b4..3f109b53bd9 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/first/AccessControlValidatorTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/first/AccessControlValidatorTest.java
@@ -8,7 +8,6 @@ import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.model.VespaModel;
import org.junit.Rule;
@@ -138,9 +137,10 @@ public class AccessControlValidatorTest {
ApplicationPackage app = new MockApplicationPackage.Builder()
.withServices(servicesXml)
.build();
+
DeployState.Builder builder = new DeployState.Builder()
.applicationPackage(app)
- .zone(new Zone(SystemName.cd, Environment.prod, RegionName.from("foo")) )// TODO: remove cd setting
+ .zone(new Zone(Environment.prod, RegionName.from("foo")) )
.properties(new DeployProperties.Builder()
.hostedVespa(true)
.build());
diff --git a/eval/CMakeLists.txt b/eval/CMakeLists.txt
index 3db05c09613..22479952270 100644
--- a/eval/CMakeLists.txt
+++ b/eval/CMakeLists.txt
@@ -42,6 +42,7 @@ vespa_define_module(
src/tests/tensor/tensor_performance
src/tests/tensor/tensor_serialization
src/tests/tensor/tensor_slime_serialization
+ src/tests/tensor/typed_cells
src/tests/tensor/vector_from_doubles_function
LIBS
diff --git a/eval/src/tests/tensor/typed_cells/CMakeLists.txt b/eval/src/tests/tensor/typed_cells/CMakeLists.txt
new file mode 100644
index 00000000000..d57ff33eda6
--- /dev/null
+++ b/eval/src/tests/tensor/typed_cells/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(eval_typed_cells_test_app TEST
+ SOURCES
+ typed_cells_test.cpp
+ DEPENDS
+ vespaeval
+)
+vespa_add_test(NAME eval_typed_cells_test_app COMMAND eval_typed_cells_test_app)
diff --git a/eval/src/tests/tensor/typed_cells/typed_cells_test.cpp b/eval/src/tests/tensor/typed_cells/typed_cells_test.cpp
new file mode 100644
index 00000000000..ccb522fd496
--- /dev/null
+++ b/eval/src/tests/tensor/typed_cells/typed_cells_test.cpp
@@ -0,0 +1,622 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/util/arrayref.h>
+#include <memory>
+
+using namespace vespalib;
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// Low-level typed cells reference
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+enum class CellType : char { DOUBLE, FLOAT, INT };
+template <typename T> bool check_type(CellType type);
+template <> bool check_type<double>(CellType type) { return (type == CellType::DOUBLE); }
+template <> bool check_type<float>(CellType type) { return (type == CellType::FLOAT); }
+template <> bool check_type<int>(CellType type) { return (type == CellType::INT); }
+
+struct TypedCells {
+ const void *data;
+ CellType type;
+ size_t size:56;
+ explicit TypedCells(ConstArrayRef<double> cells) : data(cells.begin()), type(CellType::DOUBLE), size(cells.size()) {}
+ explicit TypedCells(ConstArrayRef<float> cells) : data(cells.begin()), type(CellType::FLOAT), size(cells.size()) {}
+ explicit TypedCells(ConstArrayRef<int> cells) : data(cells.begin()), type(CellType::INT), size(cells.size()) {}
+ template <typename T> bool check_type() const { return ::check_type<T>(type); }
+ template <typename T> ConstArrayRef<T> typify() const {
+ assert(check_type<T>());
+ return ConstArrayRef<T>((const T *)data, size);
+ }
+ template <typename T> ConstArrayRef<T> unsafe_typify() const {
+ return ConstArrayRef<T>((const T *)data, size);
+ }
+};
+
+TEST("require that structures are of expected size") {
+ EXPECT_EQUAL(sizeof(void*), 8u);
+ EXPECT_EQUAL(sizeof(size_t), 8u);
+ EXPECT_EQUAL(sizeof(CellType), 1u);
+ EXPECT_EQUAL(sizeof(TypedCells), 16u);
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// CASE STUDY: Direct dispatch, minimal runtime type resolving
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+struct CellwiseAdd {
+ template <typename A, typename B, typename C>
+ static void call(const ConstArrayRef<A> &a, const ConstArrayRef<B> &b, const ConstArrayRef<C> &c, size_t cnt) __attribute__ ((noinline));
+};
+
+template <typename A, typename B, typename C>
+void CellwiseAdd::call(const ConstArrayRef<A> &a, const ConstArrayRef<B> &b, const ConstArrayRef<C> &c, size_t cnt) {
+ auto dst = unconstify(c);
+ for (size_t i = 0; i < cnt; ++i) {
+ dst[i] = a[i] + b[i];
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+struct DotProduct {
+ template <typename A, typename B>
+ static double call(const ConstArrayRef<A> &a, const ConstArrayRef<B> &b, size_t cnt) __attribute__ ((noinline));
+};
+
+template <typename A, typename B>
+double DotProduct::call(const ConstArrayRef<A> &a, const ConstArrayRef<B> &b, size_t cnt) {
+ double result = 0.0;
+ for (size_t i = 0; i < cnt; ++i) {
+ result += (a[i] * b[i]);
+ }
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+struct Sum {
+ template <typename A>
+ static double call(const ConstArrayRef<A> &a) __attribute__ ((noinline));
+};
+
+template <typename A>
+double Sum::call(const ConstArrayRef<A> &a) {
+ double result = 0.0;
+ for (const auto &value: a) {
+ result += value;
+ }
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+template <typename T>
+struct Typify {
+ template <typename... Args>
+ static auto typify_1(const TypedCells &a, Args &&...args) {
+ switch(a.type) {
+ case CellType::DOUBLE: return T::call(a.unsafe_typify<double>(), std::forward<Args>(args)...);
+ case CellType::FLOAT: return T::call(a.unsafe_typify<float>(), std::forward<Args>(args)...);
+ case CellType::INT: return T::call(a.unsafe_typify<int>(), std::forward<Args>(args)...);
+ }
+ abort();
+ }
+ template <typename A, typename... Args>
+ static auto typify_2(A &&a, const TypedCells &b, Args &&...args) {
+ switch(b.type) {
+ case CellType::DOUBLE: return T::call(std::forward<A>(a), b.unsafe_typify<double>(), std::forward<Args>(args)...);
+ case CellType::FLOAT: return T::call(std::forward<A>(a), b.unsafe_typify<float>(), std::forward<Args>(args)...);
+ case CellType::INT: return T::call(std::forward<A>(a), b.unsafe_typify<int>(), std::forward<Args>(args)...);
+ }
+ abort();
+ }
+ template <typename A, typename B, typename... Args>
+ static auto typify_3(A &&a, B &&b, const TypedCells &c, Args &&...args) {
+ switch(c.type) {
+ case CellType::DOUBLE: return T::call(std::forward<A>(a), std::forward<B>(b), c.unsafe_typify<double>(), std::forward<Args>(args)...);
+ case CellType::FLOAT: return T::call(std::forward<A>(a), std::forward<B>(b), c.unsafe_typify<float>(), std::forward<Args>(args)...);
+ case CellType::INT: return T::call(std::forward<A>(a), std::forward<B>(b), c.unsafe_typify<int>(), std::forward<Args>(args)...);
+ }
+ abort();
+ }
+};
+
+template <typename Fun>
+struct Dispatch3 {
+ using Self = Dispatch3<Fun>;
+ template <typename A, typename B, typename C, typename... Args>
+ static auto call(const ConstArrayRef<A> &a, const ConstArrayRef<B> &b, const ConstArrayRef<C> &c, Args &&...args) {
+ return Fun::call(a, b, c, std::forward<Args>(args)...);
+ }
+ template <typename A, typename B, typename... Args>
+ static auto call(const ConstArrayRef<A> &a, const ConstArrayRef<B> &b, const TypedCells &c, Args &&...args) {
+ return Typify<Self>::typify_3(a, b, c, std::forward<Args>(args)...);
+ }
+ template <typename A, typename... Args>
+ static auto call(const ConstArrayRef<A> &a, const TypedCells &b, const TypedCells &c, Args &&...args) {
+ return Typify<Self>::typify_2(a, b, c, std::forward<Args>(args)...);
+ }
+ template <typename A, typename C, typename... Args>
+ static auto call(const ConstArrayRef<A> &a, const TypedCells &b, const ConstArrayRef<C> &c, Args &&...args) {
+ return Typify<Self>::typify_2(a, b, c, std::forward<Args>(args)...);
+ }
+ template <typename... Args>
+ static auto call(const TypedCells &a, const TypedCells &b, const TypedCells &c, Args &&...args) {
+ return Typify<Self>::typify_1(a, b, c, std::forward<Args>(args)...);
+ }
+ template <typename B, typename... Args>
+ static auto call(const TypedCells &a, const ConstArrayRef<B> &b, const TypedCells &c, Args &&...args) {
+ return Typify<Self>::typify_1(a, b, c, std::forward<Args>(args)...);
+ }
+ template <typename C, typename... Args>
+ static auto call(const TypedCells &a, const TypedCells &b, const ConstArrayRef<C> &c, Args &&...args) {
+ return Typify<Self>::typify_1(a, b, c, std::forward<Args>(args)...);
+ }
+ template <typename B, typename C, typename... Args>
+ static auto call(const TypedCells &a, const ConstArrayRef<B> &b, const ConstArrayRef<C> &c, Args &&...args) {
+ return Typify<Self>::typify_1(a, b, c, std::forward<Args>(args)...);
+ }
+};
+
+template <typename Fun>
+struct Dispatch2 {
+ using Self = Dispatch2<Fun>;
+ template <typename A, typename B, typename... Args>
+ static auto call(const ConstArrayRef<A> &a, const ConstArrayRef<B> &b, Args &&...args) {
+ return Fun::call(a, b, std::forward<Args>(args)...);
+ }
+ template <typename A, typename... Args>
+ static auto call(const ConstArrayRef<A> &a, const TypedCells &b, Args &&...args) {
+ return Typify<Self>::typify_2(a, b, std::forward<Args>(args)...);
+ }
+ template <typename... Args>
+ static auto call(const TypedCells &a, const TypedCells &b, Args &&...args) {
+ return Typify<Self>::typify_1(a, b, std::forward<Args>(args)...);
+ }
+ template <typename B, typename... Args>
+ static auto call(const TypedCells &a, const ConstArrayRef<B> &b, Args &&...args) {
+ return Typify<Self>::typify_1(a, b, std::forward<Args>(args)...);
+ }
+};
+
+template <typename Fun>
+struct Dispatch1 {
+ using Self = Dispatch1<Fun>;
+ template <typename A, typename... Args>
+ static auto call(const ConstArrayRef<A> &a, Args &&...args) {
+ return Fun::call(a, std::forward<Args>(args)...);
+ }
+ template <typename... Args>
+ static auto call(const TypedCells &a, Args &&...args) {
+ return Typify<Self>::typify_1(a, std::forward<Args>(args)...);
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+TEST("require that direct dispatch 'a op b -> c' works") {
+ std::vector<int> a({1,2,3});
+ std::vector<float> b({1.5,2.5,3.5});
+ std::vector<double> c(3, 0.0);
+ ConstArrayRef<int> a_ref(a);
+ ConstArrayRef<float> b_ref(b);
+ ConstArrayRef<double> c_ref(c);
+ TypedCells a_cells(a);
+ TypedCells b_cells(b);
+ TypedCells c_cells(c);
+
+ Dispatch3<CellwiseAdd>::call(a_cells, b_cells, c_cells, 3);
+ Dispatch3<CellwiseAdd>::call(a_cells, b_ref, c_cells, 3);
+ Dispatch3<CellwiseAdd>::call(a_cells, b_cells, c_ref, 3);
+ Dispatch3<CellwiseAdd>::call(a_cells, b_ref, c_ref, 3);
+ Dispatch3<CellwiseAdd>::call(a_ref, b_cells, c_cells, 3);
+ Dispatch3<CellwiseAdd>::call(a_ref, b_cells, c_ref, 3);
+ Dispatch3<CellwiseAdd>::call(a_ref, b_ref, c_cells, 3);
+ Dispatch3<CellwiseAdd>::call(a_ref, b_ref, c_ref, 3);
+
+ EXPECT_EQUAL(c[0], 2.5);
+ EXPECT_EQUAL(c[1], 4.5);
+ EXPECT_EQUAL(c[2], 6.5);
+}
+
+TEST("require that direct dispatch 'dot product' with return value works") {
+ std::vector<int> a({1,2,3});
+ std::vector<float> b({1.5,2.5,3.5});
+ ConstArrayRef<int> a_ref(a);
+ ConstArrayRef<float> b_ref(b);
+ TypedCells a_cells(a);
+ TypedCells b_cells(b);
+ double expect = 1.5 + (2 * 2.5) + (3 * 3.5);
+
+ EXPECT_EQUAL(expect, Dispatch2<DotProduct>::call(a_cells, b_cells, 3));
+ EXPECT_EQUAL(expect, Dispatch2<DotProduct>::call(a_cells, b_ref, 3));
+ EXPECT_EQUAL(expect, Dispatch2<DotProduct>::call(a_ref, b_cells, 3));
+ EXPECT_EQUAL(expect, Dispatch2<DotProduct>::call(a_ref, b_ref, 3));
+}
+
+TEST("require that direct dispatch 'sum' with return value works") {
+ std::vector<int> a({1,2,3});
+ ConstArrayRef<int> a_ref(a);
+ TypedCells a_cells(a);
+ double expect = (1 + 2 + 3);
+
+ EXPECT_EQUAL(expect, Dispatch1<Sum>::call(a_cells));
+ EXPECT_EQUAL(expect, Dispatch1<Sum>::call(a_ref));
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// CASE STUDY: Pre-resolved templated subclass
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+struct CellwiseAdd2 {
+ virtual void call(const TypedCells &a, const TypedCells &b, const TypedCells &c, size_t cnt) const = 0;
+ template <typename A, typename B, typename C>
+ static std::unique_ptr<CellwiseAdd2> create();
+ virtual ~CellwiseAdd2() {}
+};
+
+template <typename A, typename B, typename C>
+struct CellwiseAdd2Impl : CellwiseAdd2 {
+ void call_impl(const ConstArrayRef<A> &a, const ConstArrayRef<B> &b, const ConstArrayRef<C> &c, size_t cnt) const {
+ auto dst = unconstify(c);
+ for (size_t i = 0; i < cnt; ++i) {
+ dst[i] = a[i] + b[i];
+ }
+ }
+ void call(const TypedCells &a, const TypedCells &b, const TypedCells &c, size_t cnt) const override {
+ call_impl(a.unsafe_typify<A>(), b.unsafe_typify<B>(), c.unsafe_typify<C>(), cnt);
+ }
+};
+
+template <typename A, typename B, typename C>
+std::unique_ptr<CellwiseAdd2> CellwiseAdd2::create() {
+ return std::make_unique<CellwiseAdd2Impl<A, B, C> >();
+}
+
+//-----------------------------------------------------------------------------
+
+struct DotProduct2 {
+ virtual double call(const TypedCells &a, const TypedCells &b, size_t cnt) const = 0;
+ template <typename A, typename B>
+ static std::unique_ptr<DotProduct2> create();
+ virtual ~DotProduct2() {}
+};
+
+template <typename A, typename B>
+struct DotProduct2Impl : DotProduct2 {
+ double call_impl(const ConstArrayRef<A> &a, const ConstArrayRef<B> &b, size_t cnt) const {
+ double result = 0.0;
+ for (size_t i = 0; i < cnt; ++i) {
+ result += (a[i] * b[i]);
+ }
+ return result;
+ }
+ double call(const TypedCells &a, const TypedCells &b, size_t cnt) const override {
+ return call_impl(a.unsafe_typify<A>(), b.unsafe_typify<B>(), cnt);
+ }
+};
+
+template <typename A, typename B>
+std::unique_ptr<DotProduct2> DotProduct2::create() {
+ return std::make_unique<DotProduct2Impl<A, B> >();
+}
+
+//-----------------------------------------------------------------------------
+
+struct Sum2 {
+ virtual double call(const TypedCells &a) const = 0;
+ template <typename A>
+ static std::unique_ptr<Sum2> create();
+ virtual ~Sum2() {}
+};
+
+template <typename A>
+struct Sum2Impl : Sum2 {
+ double call_impl(const ConstArrayRef<A> &a) const {
+ double result = 0.0;
+ for (const auto &value: a) {
+ result += value;
+ }
+ return result;
+ }
+ double call(const TypedCells &a) const override {
+ return call_impl(a.unsafe_typify<A>());
+ }
+};
+
+template <typename A>
+std::unique_ptr<Sum2> Sum2::create() {
+ return std::make_unique<Sum2Impl<A> >();
+}
+
+//-----------------------------------------------------------------------------
+
+template <typename T, typename... Args>
+std::unique_ptr<T> create(CellType a_type) {
+ switch(a_type) {
+ case CellType::DOUBLE: return T::template create<double, Args...>();
+ case CellType::FLOAT: return T::template create<float, Args...>();
+ case CellType::INT: return T::template create<int, Args...>();
+ }
+ abort();
+}
+
+template <typename T, typename... Args>
+std::unique_ptr<T> create(CellType a_type, CellType b_type) {
+ switch(b_type) {
+ case CellType::DOUBLE: return create<T, double, Args...>(a_type);
+ case CellType::FLOAT: return create<T, float, Args...>(a_type);
+ case CellType::INT: return create<T, int, Args...>(a_type);
+ }
+ abort();
+}
+
+template <typename T>
+std::unique_ptr<T> create(CellType a_type, CellType b_type, CellType c_type) {
+ switch(c_type) {
+ case CellType::DOUBLE: return create<T, double>(a_type, b_type);
+ case CellType::FLOAT: return create<T, float>(a_type, b_type);
+ case CellType::INT: return create<T, int>(a_type, b_type);
+ }
+ abort();
+}
+
+//-----------------------------------------------------------------------------
+
+TEST("require that pre-resolved subclass 'a op b -> c' works") {
+ std::vector<int> a({1,2,3});
+ std::vector<float> b({1.5,2.5,3.5});
+ std::vector<double> c(3, 0.0);
+ TypedCells a_cells(a);
+ TypedCells b_cells(b);
+ TypedCells c_cells(c);
+
+ auto op = create<CellwiseAdd2>(a_cells.type, b_cells.type, c_cells.type);
+ op->call(a_cells, b_cells, c_cells, 3);
+
+ EXPECT_EQUAL(c[0], 2.5);
+ EXPECT_EQUAL(c[1], 4.5);
+ EXPECT_EQUAL(c[2], 6.5);
+}
+
+TEST("require that pre-resolved subclass 'dot product' with return value works") {
+ std::vector<int> a({1,2,3});
+ std::vector<float> b({1.5,2.5,3.5});
+ TypedCells a_cells(a);
+ TypedCells b_cells(b);
+ double expect = 1.5 + (2 * 2.5) + (3 * 3.5);
+
+ auto op = create<DotProduct2>(a_cells.type, b_cells.type);
+
+ EXPECT_EQUAL(expect, op->call(a_cells, b_cells, 3));
+}
+
+TEST("require that pre-resolved subclass 'sum' with return value works") {
+ std::vector<int> a({1,2,3});
+ TypedCells a_cells(a);
+ double expect = (1 + 2 + 3);
+
+ auto op = create<Sum2>(a_cells.type);
+
+ EXPECT_EQUAL(expect, op->call(a_cells));
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// CASE STUDY: self-updating cached function pointer
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+template <typename T, typename... Args>
+auto get_fun(CellType a_type) {
+ switch(a_type) {
+ case CellType::DOUBLE: return T::template get_fun<double, Args...>();
+ case CellType::FLOAT: return T::template get_fun<float, Args...>();
+ case CellType::INT: return T::template get_fun<int, Args...>();
+ }
+ abort();
+}
+
+template <typename T, typename... Args>
+auto get_fun(CellType a_type, CellType b_type) {
+ switch(b_type) {
+ case CellType::DOUBLE: return get_fun<T, double, Args...>(a_type);
+ case CellType::FLOAT: return get_fun<T, float, Args...>(a_type);
+ case CellType::INT: return get_fun<T, int, Args...>(a_type);
+ }
+ abort();
+}
+
+template <typename T>
+auto get_fun(CellType a_type, CellType b_type, CellType c_type) {
+ switch(c_type) {
+ case CellType::DOUBLE: return get_fun<T, double>(a_type, b_type);
+ case CellType::FLOAT: return get_fun<T, float>(a_type, b_type);
+ case CellType::INT: return get_fun<T, int>(a_type, b_type);
+ }
+ abort();
+}
+
+//-----------------------------------------------------------------------------
+
+struct CellwiseAdd3 {
+ struct Self;
+ using fun_type = void (*)(const TypedCells &x, const TypedCells &y, const TypedCells &z, size_t cnt, Self &self);
+ template <typename A, typename B, typename C>
+ static fun_type get_fun();
+ struct Self {
+ fun_type my_fun;
+ Self();
+ };
+ Self self;
+ void call(const TypedCells &x, const TypedCells &y, const TypedCells &z, size_t cnt) {
+ self.my_fun(x, y, z, cnt, self);
+ }
+};
+
+template <typename A, typename B, typename C>
+void cellwise_add(const TypedCells &x, const TypedCells &y, const TypedCells &z, size_t cnt, CellwiseAdd3::Self &self) {
+ if (!x.check_type<A>() || !y.check_type<B>() || !z.check_type<C>()) {
+ auto new_fun = get_fun<CellwiseAdd3>(x.type, y.type, z.type);
+ self.my_fun = new_fun;
+ return new_fun(x, y, z, cnt, self);
+ }
+ auto a = x.unsafe_typify<A>();
+ auto b = y.unsafe_typify<B>();
+ auto c = z.unsafe_typify<C>();
+ auto dst = unconstify(c);
+ for (size_t i = 0; i < cnt; ++i) {
+ dst[i] = a[i] + b[i];
+ }
+};
+
+template <typename A, typename B, typename C>
+CellwiseAdd3::fun_type CellwiseAdd3::get_fun() {
+ return cellwise_add<A, B, C>;
+}
+
+CellwiseAdd3::Self::Self()
+ : my_fun(cellwise_add<double, double, double>)
+{
+}
+
+//-----------------------------------------------------------------------------
+
+struct DotProduct3 {
+ struct Self;
+ using fun_type = double (*)(const TypedCells &x, const TypedCells &y, size_t cnt, Self &self);
+ template <typename A, typename B>
+ static fun_type get_fun();
+ struct Self {
+ fun_type my_fun;
+ Self();
+ };
+ Self self;
+ double call(const TypedCells &x, const TypedCells &y, size_t cnt) {
+ return self.my_fun(x, y, cnt, self);
+ }
+};
+
+template <typename A, typename B>
+double dot_product(const TypedCells &x, const TypedCells &y, size_t cnt, DotProduct3::Self &self) {
+ if (!x.check_type<A>() || !y.check_type<B>()) {
+ auto new_fun = get_fun<DotProduct3>(x.type, y.type);
+ self.my_fun = new_fun;
+ return new_fun(x, y, cnt, self);
+ }
+ auto a = x.unsafe_typify<A>();
+ auto b = y.unsafe_typify<B>();
+ double result = 0.0;
+ for (size_t i = 0; i < cnt; ++i) {
+ result += (a[i] * b[i]);
+ }
+ return result;
+}
+
+template <typename A, typename B>
+DotProduct3::fun_type DotProduct3::get_fun() {
+ return dot_product<A, B>;
+}
+
+DotProduct3::Self::Self()
+ : my_fun(dot_product<double, double>)
+{
+}
+
+//-----------------------------------------------------------------------------
+
+struct Sum3 {
+ struct Self;
+ using fun_type = double (*)(const TypedCells &x, Self &self);
+ template <typename A>
+ static fun_type get_fun();
+ struct Self {
+ fun_type my_fun;
+ Self();
+ };
+ Self self;
+ double call(const TypedCells &x) {
+ return self.my_fun(x, self);
+ }
+};
+
+template <typename A>
+double sum(const TypedCells &x, Sum3::Self &self) {
+ if (!x.check_type<A>()) {
+ auto new_fun = get_fun<Sum3>(x.type);
+ self.my_fun = new_fun;
+ return new_fun(x, self);
+ }
+ auto a = x.unsafe_typify<A>();
+ double result = 0.0;
+ for (const auto &value: a) {
+ result += value;
+ }
+ return result;
+}
+
+template <typename A>
+Sum3::fun_type Sum3::get_fun() {
+ return sum<A>;
+}
+
+Sum3::Self::Self()
+ : my_fun(sum<double>)
+{
+}
+
+//-----------------------------------------------------------------------------
+
+TEST("require that self-updating cached function pointer 'a op b -> c' works") {
+ std::vector<int> a({1,2,3});
+ std::vector<float> b({1.5,2.5,3.5});
+ std::vector<double> c(3, 0.0);
+ TypedCells a_cells(a);
+ TypedCells b_cells(b);
+ TypedCells c_cells(c);
+
+ CellwiseAdd3 op;
+ EXPECT_EQUAL(op.self.my_fun, (&cellwise_add<double,double,double>));
+ op.call(a_cells, b_cells, c_cells, 3);
+ EXPECT_EQUAL(op.self.my_fun, (&cellwise_add<int,float,double>));
+ EXPECT_NOT_EQUAL(op.self.my_fun, (&cellwise_add<double,double,double>));
+
+ EXPECT_EQUAL(c[0], 2.5);
+ EXPECT_EQUAL(c[1], 4.5);
+ EXPECT_EQUAL(c[2], 6.5);
+}
+
+TEST("require that self-updating cached function pointer 'dot product' with return value works") {
+ std::vector<int> a({1,2,3});
+ std::vector<float> b({1.5,2.5,3.5});
+ TypedCells a_cells(a);
+ TypedCells b_cells(b);
+ double expect = 1.5 + (2 * 2.5) + (3 * 3.5);
+
+ DotProduct3 op;
+ EXPECT_EQUAL(op.self.my_fun, (&dot_product<double,double>));
+ EXPECT_EQUAL(expect, op.call(a_cells, b_cells, 3));
+ EXPECT_EQUAL(op.self.my_fun, (&dot_product<int,float>));
+ EXPECT_NOT_EQUAL(op.self.my_fun, (&dot_product<double,double>));
+}
+
+TEST("require that self-updating cached function pointer 'sum' with return value works") {
+ std::vector<int> a({1,2,3});
+ TypedCells a_cells(a);
+ double expect = (1 + 2 + 3);
+
+ Sum3 op;
+ EXPECT_EQUAL(op.self.my_fun, (&sum<double>));
+ EXPECT_EQUAL(expect, op.call(a_cells));
+ EXPECT_EQUAL(op.self.my_fun, (&sum<int>));
+ EXPECT_NOT_EQUAL(op.self.my_fun, (&sum<double>));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
index 4bf7e70d06b..ade2c94a25c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
@@ -40,6 +40,7 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* The hosted Vespa production node repository, which stores its state in Zookeeper.
@@ -394,12 +395,31 @@ public class NodeRepository extends AbstractComponent {
return db.writeTo(Node.State.dirty, node, agent, Optional.of(reason));
}
- public Node setDirty(String hostname, Agent agent, String reason) {
- Node node = getNode(hostname, Node.State.provisioned, Node.State.failed, Node.State.parked).orElseThrow(() ->
- new IllegalArgumentException("Could not deallocate " + hostname +
- ": No such node in the provisioned, failed or parked state"));
+ public List<Node> dirtyRecursively(String hostname, Agent agent, String reason) {
+ Node nodeToDirty = getNode(hostname).orElseThrow(() ->
+ new IllegalArgumentException("Could not deallocate " + hostname + ": Node not found"));
- return setDirty(node, agent, reason);
+ List<Node> nodesToDirty =
+ (nodeToDirty.type().isDockerHost() ?
+ Stream.concat(getChildNodes(hostname).stream(), Stream.of(nodeToDirty)) :
+ Stream.of(nodeToDirty))
+ .filter(node -> node.state() != Node.State.dirty)
+ .collect(Collectors.toList());
+
+ List<String> hostnamesNotAllowedToDirty = nodesToDirty.stream()
+ .filter(node -> node.state() != Node.State.provisioned)
+ .filter(node -> node.state() != Node.State.failed)
+ .filter(node -> node.state() != Node.State.parked)
+ .map(Node::hostname)
+ .collect(Collectors.toList());
+ if (!hostnamesNotAllowedToDirty.isEmpty()) {
+ throw new IllegalArgumentException("Could not deallocate " + hostname + ": " +
+ String.join(", ", hostnamesNotAllowedToDirty) + " must be in either provisioned, failed or parked state");
+ }
+
+ return nodesToDirty.stream()
+ .map(node -> setDirty(node, agent, reason))
+ .collect(Collectors.toList());
}
/**
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 2f7d3120211..03777078251 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
@@ -34,7 +34,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -122,8 +121,9 @@ public class NodesApiHandler extends LoggingRequestHandler {
return new MessageResponse("Moved " + parkedHostnames + " to parked");
}
else if (path.startsWith("/nodes/v2/state/dirty/")) {
- nodeRepository.setDirty(lastElement(path), Agent.operator, "Dirtied through the nodes/v2 API");
- return new MessageResponse("Moved " + lastElement(path) + " to dirty");
+ List<Node> dirtiedNodes = nodeRepository.dirtyRecursively(lastElement(path), Agent.operator, "Dirtied through the nodes/v2 API");
+ String dirtiedHostnames = dirtiedNodes.stream().map(Node::hostname).sorted().collect(Collectors.joining(", "));
+ return new MessageResponse("Moved " + dirtiedHostnames + " to dirty");
}
else if (path.startsWith("/nodes/v2/state/active/")) {
nodeRepository.reactivate(lastElement(path), Agent.operator, "Reactivated through nodes/v2 API");
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
index a21d03ff603..931ddc5e051 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
@@ -108,7 +108,7 @@ public class MockNodeRepository extends NodeRepository {
setReady(nodes, Agent.system, getClass().getSimpleName());
fail("host5.yahoo.com", Agent.system, getClass().getSimpleName());
- setDirty("host55.yahoo.com", Agent.system, getClass().getSimpleName());
+ dirtyRecursively("host55.yahoo.com", Agent.system, getClass().getSimpleName());
ApplicationId zoneApp = ApplicationId.from(TenantName.from("zoneapp"), ApplicationName.from("zoneapp"), InstanceName.from("zoneapp"));
ClusterSpec zoneCluster = ClusterSpec.request(ClusterSpec.Type.container,
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java
index 6e97f3d1269..5d217ce86e5 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTest.java
@@ -11,7 +11,12 @@ import com.yahoo.vespa.hosted.provision.node.Agent;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
import static junit.framework.TestCase.fail;
import static org.junit.Assert.assertEquals;
@@ -72,7 +77,7 @@ public class NodeRepositoryTest {
// Expected
}
- tester.nodeRepository().setDirty("host1", Agent.system, getClass().getSimpleName());
+ tester.nodeRepository().dirtyRecursively("host1", Agent.system, getClass().getSimpleName());
tester.nodeRepository().setReady("host1", Agent.system, getClass().getSimpleName());
tester.nodeRepository().removeRecursively("host1");
}
@@ -89,7 +94,7 @@ public class NodeRepositoryTest {
tester.addNode("node20", "node20", "host2", "docker", NodeType.tenant);
assertEquals(6, tester.nodeRepository().getNodes().size());
- tester.nodeRepository().setDirty("node11", Agent.system, getClass().getSimpleName());
+ tester.setNodeState("node11", Node.State.dirty);
try {
tester.nodeRepository().removeRecursively("host1");
@@ -111,4 +116,46 @@ public class NodeRepositoryTest {
tester.nodeRepository().removeRecursively("host1");
assertEquals(0, tester.nodeRepository().getNodes().size());
}
+
+ @Test
+ public void dirty_host_only_if_we_can_dirty_children() {
+ NodeRepositoryTester tester = new NodeRepositoryTester();
+
+ tester.addNode("id1", "host1", "default", NodeType.host);
+ tester.addNode("id2", "host2", "default", NodeType.host);
+ tester.addNode("node10", "node10", "host1", "docker", NodeType.tenant);
+ tester.addNode("node11", "node11", "host1", "docker", NodeType.tenant);
+ tester.addNode("node12", "node12", "host1", "docker", NodeType.tenant);
+ tester.addNode("node20", "node20", "host2", "docker", NodeType.tenant);
+
+ tester.setNodeState("node11", Node.State.ready);
+ tester.setNodeState("node12", Node.State.active);
+ tester.setNodeState("node20", Node.State.failed);
+
+ assertEquals(6, tester.nodeRepository().getNodes().size());
+
+ // Should be OK to dirty host2 as it is in provisioned and its only child is in failed
+ tester.nodeRepository().dirtyRecursively("host2", Agent.system, NodeRepositoryTest.class.getSimpleName());
+ assertEquals(asSet("host2", "node20"), filterNodes(tester, node -> node.state() == Node.State.dirty));
+
+ // Cant dirty host1, node11 is ready and node12 is active
+ try {
+ tester.nodeRepository().dirtyRecursively("host1", Agent.system, NodeRepositoryTest.class.getSimpleName());
+ fail("Should not be able to dirty host1");
+ } catch (IllegalArgumentException ignored) { } // Expected;
+
+ assertEquals(asSet("host2", "node20"), filterNodes(tester, node -> node.state() == Node.State.dirty));
+ }
+
+ private static Set<String> asSet(String... elements) {
+ return new HashSet<>(Arrays.asList(elements));
+ }
+
+ private static Set<String> filterNodes(NodeRepositoryTester tester, Predicate<Node> filter) {
+ return tester.nodeRepository()
+ .getNodes().stream()
+ .filter(filter)
+ .map(Node::hostname)
+ .collect(Collectors.toSet());
+ }
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTester.java
index 1a42a71863d..03ada1e7951 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/NodeRepositoryTester.java
@@ -9,6 +9,7 @@ import com.yahoo.config.provision.Zone;
import com.yahoo.config.provisioning.FlavorsConfig;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.curator.mock.MockCurator;
+import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.provisioning.FlavorConfigBuilder;
import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver;
@@ -58,6 +59,16 @@ public class NodeRepositoryTester {
return nodeRepository.addNodes(Collections.singletonList(node)).get(0);
}
+ /**
+ * Moves a node directly to the given state without doing any validation, useful
+ * to create wanted test scenario without having to move every node through series
+ * of valid state transitions
+ */
+ public void setNodeState(String hostname, Node.State state) {
+ Node node = nodeRepository.getNode(hostname).orElseThrow(RuntimeException::new);
+ nodeRepository.database().writeTo(state, node, Agent.system, Optional.empty());
+ }
+
private FlavorsConfig createConfig() {
FlavorConfigBuilder b = new FlavorConfigBuilder();
b.addFlavor("default", 2., 4., 100, Flavor.Type.BARE_METAL).cost(3);
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/monitoring/MetricsReporterTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/monitoring/MetricsReporterTest.java
index 622732460c0..5e7c6787973 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/monitoring/MetricsReporterTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/monitoring/MetricsReporterTest.java
@@ -125,7 +125,7 @@ public class MetricsReporterTest {
Node dockerHost = Node.create("openStackId1", Collections.singleton("::1"), additionalIps, "dockerHost", Optional.empty(), nodeFlavors.getFlavorOrThrow("host"), NodeType.host);
nodeRepository.addNodes(Collections.singletonList(dockerHost));
- nodeRepository.setDirty("dockerHost", Agent.system, getClass().getSimpleName());
+ nodeRepository.dirtyRecursively("dockerHost", Agent.system, getClass().getSimpleName());
nodeRepository.setReady("dockerHost", Agent.system, getClass().getSimpleName());
Node container1 = Node.createDockerNode("openStackId1:1", Collections.singleton("::2"), Collections.emptySet(), "container1", Optional.of("dockerHost"), nodeFlavors.getFlavorOrThrow("docker"), NodeType.tenant);
diff --git a/parent/pom.xml b/parent/pom.xml
index 465771de9cf..7f22c5ccda4 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -116,7 +116,10 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
- <version>2.5</version>
+ <version>2.8.1</version>
+ <configuration>
+ <retryFailedDeploymentCount>10</retryFailedDeploymentCount>
+ </configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>