diff options
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> |