diff options
author | Valerij Fredriksen <valerijf@verizonmedia.com> | 2021-09-27 14:57:08 +0200 |
---|---|---|
committer | Valerij Fredriksen <valerijf@verizonmedia.com> | 2021-09-27 14:57:08 +0200 |
commit | 8f527a35bc80ac3cd936db92d7b3ba4efde2d9ec (patch) | |
tree | aaba30a621487c1f536ec750d496d2baf89ddf25 | |
parent | 1e630a4f57ba52f3194895837d2f17b8cbc56ada (diff) | |
parent | c4feaca0cfbf19b18c7b2ced33a1e18f80534b38 (diff) |
Merge branch 'master' into ogronnesby/create-application-on-deploy
179 files changed, 1930 insertions, 1985 deletions
diff --git a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java index f7398d0478f..b4a0c3bd578 100644 --- a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java +++ b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/ServiceCluster.java @@ -113,6 +113,14 @@ public class ServiceCluster { } public String nodeDescription(boolean plural) { + return entityDescription("node", plural); + } + + public String serviceDescription(boolean plural) { + return entityDescription("service", plural); + } + + private String entityDescription(String entity, boolean plural) { String pluralSuffix = plural ? "s" : ""; return isConfigServer() ? "config server" + pluralSuffix : isConfigServerHost() ? "config server host" + pluralSuffix : @@ -121,7 +129,7 @@ public class ServiceCluster { isProxy() ? (plural ? "proxies" : "proxy") : isProxyHost() ? "proxy host" + pluralSuffix : isTenantHost() ? "tenant host" + pluralSuffix : - "node" + pluralSuffix + " of {" + serviceType + "," + clusterId + "}"; + entity + pluralSuffix + " of {" + serviceType + "," + clusterId + "}"; } private boolean isHostedVespaApplicationWithId(ApplicationInstanceId id) { diff --git a/client/go/Makefile b/client/go/Makefile index 17748d765c8..a86feb456ef 100644 --- a/client/go/Makefile +++ b/client/go/Makefile @@ -2,7 +2,7 @@ # The version to release. Defaults to the current tag or revision. # Use env VERSION=X.Y.Z make ... to override -VERSION ?= $(shell git describe --tags 2> /dev/null | sed -E "s/^vespa-|-1$$//g") +VERSION ?= $(shell git describe --tags 2> /dev/null | sed "s/^v//") DEVEL_VERSION := $(shell echo "0.0.0-`git rev-parse --short HEAD`") ifeq ($(VERSION),) VERSION = $(DEVEL_VERSION) @@ -26,10 +26,11 @@ all: test checkfmt install # # Example: # -# $ git checkout vespa-X.Y.Z-1 -# $ make dist-github +# $ git checkout vX.Y.Z +# $ make dist-homebrew dist-homebrew: dist-version - brew bump-formula-pr --tag vespa-$(VERSION)-1 --version $(VERSION) vespa-cli +# TODO(mpolden): Remove --version=0 after next release + brew bump-formula-pr --tag v$(VERSION) --version=0 vespa-cli # Create a GitHub release draft for all platforms. Note that this only creates a # draft, which is not publicly visible until it's explicitly published. @@ -40,7 +41,7 @@ dist-homebrew: dist-version # # Example: # -# $ git checkout vespa-X.Y.Z-1 +# $ git checkout vX.Y.Z # $ make dist-github dist-github: dist gh release create v$(VERSION) --repo vespa-engine/vespa --notes-file $(CURDIR)/README.md --draft --title "Vespa CLI $(VERSION)" \ @@ -83,7 +84,7 @@ dist-sha256sums: dist-version: ifeq ($(VERSION),$(DEVEL_VERSION)) - $(error Invalid release version: $(VERSION). Try 'git checkout vespa-X.Y.Z-1' or 'env VERSION=X.Y.Z make ...') + $(error Invalid release version: $(VERSION). Try 'git checkout vX.Y.Z' or 'env VERSION=X.Y.Z make ...') endif # diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java index 9ee36831d6a..d38cb1a2207 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java @@ -80,13 +80,15 @@ public interface ModelContext { @ModelFeatureFlag(owners = {"baldersheim"}) default boolean useAsyncMessageHandlingOnSchedule() { throw new UnsupportedOperationException("TODO specify default value"); } @ModelFeatureFlag(owners = {"baldersheim"}) default double feedConcurrency() { throw new UnsupportedOperationException("TODO specify default value"); } @ModelFeatureFlag(owners = {"baldersheim"}) default int metricsproxyNumThreads() { throw new UnsupportedOperationException("TODO specify default value"); } - @ModelFeatureFlag(owners = {"baldersheim"}) default boolean enforceRankProfileInheritance() { return false; } + @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter = "7.472") default boolean enforceRankProfileInheritance() { return true; } @ModelFeatureFlag(owners = {"baldersheim"}) default int largeRankExpressionLimit() { return 8192; } @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter = "7.468") default boolean useExternalRankExpressions() { return true; } @ModelFeatureFlag(owners = {"baldersheim"}, removeAfter = "7.468") default boolean distributeExternalRankExpressions() { return true; } @ModelFeatureFlag(owners = {"baldersheim"}) default int maxConcurrentMergesPerNode() { throw new UnsupportedOperationException("TODO specify default value"); } @ModelFeatureFlag(owners = {"baldersheim"}) default int maxMergeQueueSize() { throw new UnsupportedOperationException("TODO specify default value"); } @ModelFeatureFlag(owners = {"baldersheim"}) default boolean dryRunOnnxOnSetup() { return true; } + @ModelFeatureFlag(owners = {"baldersheim"}) default boolean containerDumpHeapOnShutdownTimeout() { throw new UnsupportedOperationException("TODO specify default value"); } + @ModelFeatureFlag(owners = {"baldersheim"}) default double containerShutdownTimeout() { throw new UnsupportedOperationException("TODO specify default value"); } @ModelFeatureFlag(owners = {"geirst"}) default boolean enableFeedBlockInDistributor() { return true; } @ModelFeatureFlag(owners = {"bjorncs", "tokle"}) default List<String> allowedAthenzProxyIdentities() { return List.of(); } @ModelFeatureFlag(owners = {"vekterli"}) default int maxActivationInhibitedOutOfSyncGroups() { return 0; } diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java index 4bf20e75a5d..e27e0e7624f 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java @@ -63,6 +63,8 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea private double resourceLimitDisk = 0.8; private double resourceLimitMemory = 0.8; private double minNodeRatioPerGroup = 0.0; + private boolean containerDumpHeapOnShutdownTimeout = false; + private double containerShutdownTimeout = 50.0; @Override public ModelContext.FeatureFlags featureFlags() { return this; } @Override public boolean multitenant() { return multitenant; } @@ -105,10 +107,14 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea @Override public double resourceLimitMemory() { return resourceLimitMemory; } @Override public double minNodeRatioPerGroup() { return minNodeRatioPerGroup; } @Override public int metricsproxyNumThreads() { return 1; } - @Override public boolean enforceRankProfileInheritance() { return enforceRankProfileInheritance; } - - public TestProperties enforceRankProfileInheritance(boolean value) { - enforceRankProfileInheritance = value; + @Override public double containerShutdownTimeout() { return containerShutdownTimeout; } + @Override public boolean containerDumpHeapOnShutdownTimeout() { return containerDumpHeapOnShutdownTimeout; } + public TestProperties containerDumpHeapOnShutdownTimeout(boolean value) { + containerDumpHeapOnShutdownTimeout = value; + return this; + } + public TestProperties containerShutdownTimeout(double value) { + containerShutdownTimeout = value; return this; } public TestProperties largeRankExpressionLimit(int value) { diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java index 9d51d39f3d0..d169760538d 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/RankProfile.java @@ -227,12 +227,7 @@ public class RankProfile implements Cloneable { String msg = "rank-profile '" + getName() + "' inherits '" + inheritedName + "', but it does not exist anywhere in the inheritance of search '" + ((getSearch() != null) ? getSearch().getName() : " global rank profiles") + "'."; - if (search.getDeployProperties().featureFlags().enforceRankProfileInheritance()) { - throw new IllegalArgumentException(msg); - } else { - deployLogger.logApplicationPackage(Level.WARNING, msg); - inherited = resolveIndependentOfInheritance(); - } + throw new IllegalArgumentException(msg); } else { List<String> children = new ArrayList<>(); children.add(createFullyQualifiedName()); @@ -241,12 +236,7 @@ public class RankProfile implements Cloneable { } return inherited; } - private RankProfile resolveIndependentOfInheritance() { - for (RankProfile rankProfile : rankProfileRegistry.all()) { - if (rankProfile.getName().equals(inheritedName)) return rankProfile; - } - return null; - } + private String createFullyQualifiedName() { return (search != null) ? (search.getName() + "." + getName()) diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java index 46b785ccf42..ad85f68cb8a 100644 --- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java +++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/RankProfileList.java @@ -100,12 +100,6 @@ public class RankProfileList extends Derived implements RankProfilesConfig.Produ remaining.forEach((name, rank) -> { if (areDependenciesReady(rank, rankProfileRegistry)) ready.add(rank); }); - if (ready.isEmpty() && ! deployProperties.featureFlags().enforceRankProfileInheritance()) { - // Dirty fallback to allow incorrect rankprofile inheritance to pass for now. - // We then handle one by one. - // TODO remove ASAP - ready.add(remaining.values().iterator().next()); - } processRankProfiles(ready, queryProfiles, importedModels, search, attributeFields, deployProperties, executor); ready.forEach(rank -> remaining.remove(rank.getName())); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java index b915453b593..cdf8d592391 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java @@ -74,6 +74,8 @@ public abstract class Container extends AbstractService implements private final boolean retired; /** The unique index of this node */ private final int index; + private final boolean dumpHeapOnShutdownTimeout; + private final double shutdownTimeoutS; private final ComponentGroup<Handler<?>> handlers = new ComponentGroup<>(this, "handler"); private final ComponentGroup<Component<?, ?>> components = new ComponentGroup<>(this, "components"); @@ -90,6 +92,8 @@ public abstract class Container extends AbstractService implements this.parent = parent; this.retired = retired; this.index = index; + dumpHeapOnShutdownTimeout = deployState.featureFlags().containerDumpHeapOnShutdownTimeout(); + shutdownTimeoutS = deployState.featureFlags().containerShutdownTimeout(); this.defaultHttpServer = new JettyHttpServer("DefaultHttpServer", containerClusterOrNull(parent), deployState.isHosted()); if (getHttp() == null) { addChild(defaultHttpServer); @@ -315,7 +319,9 @@ public abstract class Container extends AbstractService implements .slobrokId(serviceSlobrokId())) .filedistributor(filedistributorConfig()) .discriminator((clusterName != null ? clusterName + "." : "" ) + name) - .nodeIndex(index); + .nodeIndex(index) + .shutdown.dumpHeapOnTimeout(dumpHeapOnShutdownTimeout) + .timeout(shutdownTimeoutS); } /** Returns the jvm args set explicitly for this node */ diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java index 2c1f68e5ecc..aa068ec8f0e 100644 --- a/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java +++ b/config-model/src/test/java/com/yahoo/searchdefinition/RankProfileTestCase.java @@ -118,13 +118,8 @@ public class RankProfileTestCase extends SchemaTestCase { @Test public void requireThatSidewaysInheritanceIsImpossible() throws ParseException { - verifySidewaysInheritance(false); - verifySidewaysInheritance(true); - } - private void verifySidewaysInheritance(boolean enforce) throws ParseException { RankProfileRegistry registry = new RankProfileRegistry(); - SearchBuilder builder = new SearchBuilder(registry, setupQueryProfileTypes(), - new TestProperties().enforceRankProfileInheritance(enforce)); + SearchBuilder builder = new SearchBuilder(registry, setupQueryProfileTypes()); builder.importString(joinLines( "schema child1 {", " document child1 {", @@ -168,15 +163,8 @@ public class RankProfileTestCase extends SchemaTestCase { "}")); try { builder.build(true); - if (enforce) { - fail("Sideways inheritance should have been enforced"); - } else { - assertNotNull(builder.getSearch("child2")); - assertNotNull(builder.getSearch("child1")); - assertTrue(registry.get("child1", "child").inherits("parent")); - } + fail("Sideways inheritance should have been enforced"); } catch (IllegalArgumentException e) { - if (!enforce) fail("Sideways inheritance should have been allowed"); assertEquals("rank-profile 'child' inherits 'parent', but it does not exist anywhere in the inheritance of search 'child1'.", e.getMessage()); } } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java index 2505aa3b01e..912ca23dce2 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java @@ -7,6 +7,7 @@ import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.NullConfigModelRegistry; import com.yahoo.config.model.api.ContainerEndpoint; import com.yahoo.config.model.api.EndpointCertificateSecrets; +import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.model.api.TenantSecretStore; import com.yahoo.config.model.builder.xml.test.DomBuilderTest; import com.yahoo.config.model.deploy.DeployState; @@ -681,36 +682,47 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase { @Test public void qrconfig_is_produced() throws IOException, SAXException { + QrConfig qr = getQrConfig(new TestProperties()); + String hostname = HostName.getLocalhost(); // Using the same way of getting hostname as filedistribution model + assertEquals("default.container.0", qr.discriminator()); + assertEquals(19102, qr.rpc().port()); + assertEquals("vespa/service/default/container.0", qr.rpc().slobrokId()); + assertTrue(qr.rpc().enabled()); + assertEquals("", qr.rpc().host()); + assertFalse(qr.restartOnDeploy()); + assertEquals("filedistribution/" + hostname, qr.filedistributor().configid()); + assertEquals(50.0, qr.shutdown().timeout(), 0.00000000000001); + assertFalse(qr.shutdown().dumpHeapOnTimeout()); + } + private QrConfig getQrConfig(ModelContext.Properties properties) throws IOException, SAXException { String servicesXml = "<services>" + - "<admin version='3.0'>" + - " <nodes count='2'/>" + - "</admin>" + - "<container id ='default' version='1.0'>" + - " <nodes>" + - " <node hostalias='node1' />" + - " </nodes>" + - "</container>" + - "</services>"; + " <admin version='3.0'>" + + " <nodes count='2'/>" + + " </admin>" + + " <container id ='default' version='1.0'>" + + " <nodes>" + + " <node hostalias='node1' />" + + " </nodes>" + + " </container>" + + "</services>"; ApplicationPackage applicationPackage = new MockApplicationPackage.Builder() .withServices(servicesXml) .build(); VespaModel model = new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder() .applicationPackage(applicationPackage) - .properties(new TestProperties()) + .properties(properties) .build()); - String hostname = HostName.getLocalhost(); // Using the same way of getting hostname as filedistribution model + return model.getConfig(QrConfig.class, "default/container.0"); + } - QrConfig config = model.getConfig(QrConfig.class, "default/container.0"); - assertEquals("default.container.0", config.discriminator()); - assertEquals(19102, config.rpc().port()); - assertEquals("vespa/service/default/container.0", config.rpc().slobrokId()); - assertTrue(config.rpc().enabled()); - assertEquals("", config.rpc().host()); - assertFalse(config.restartOnDeploy()); - assertEquals("filedistribution/" + hostname, config.filedistributor().configid()); + @Test + public void control_container_shutdown() throws IOException, SAXException { + QrConfig qr = getQrConfig(new TestProperties().containerShutdownTimeout(133).containerDumpHeapOnShutdownTimeout(true)); + assertEquals(133.0, qr.shutdown().timeout(), 0.00000000000001); + assertTrue(qr.shutdown().dumpHeapOnTimeout()); } @Test diff --git a/config/src/apps/vespa-get-config/getconfig.cpp b/config/src/apps/vespa-get-config/getconfig.cpp index 273a3abd1cd..679d118d0bc 100644 --- a/config/src/apps/vespa-get-config/getconfig.cpp +++ b/config/src/apps/vespa-get-config/getconfig.cpp @@ -240,7 +240,7 @@ GetConfig::Main() printf("defNamespace %s\n", rKey.getDefNamespace().c_str()); printf("configID %s\n", rKey.getConfigId().c_str()); - printf("configMD5 %s\n", rState.md5.c_str()); + printf("configXxhash64 %s\n", rState.xxhash64.c_str()); printf("generation %" PRId64 "\n", rState.generation); printf("trace %s\n", response->getTrace().toString().c_str()); diff --git a/config/src/main/java/com/yahoo/vespa/config/protocol/JRTServerConfigRequestV3.java b/config/src/main/java/com/yahoo/vespa/config/protocol/JRTServerConfigRequestV3.java index 446ddf0560b..e6c9bc2175b 100644 --- a/config/src/main/java/com/yahoo/vespa/config/protocol/JRTServerConfigRequestV3.java +++ b/config/src/main/java/com/yahoo/vespa/config/protocol/JRTServerConfigRequestV3.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.config.protocol; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import com.yahoo.vespa.config.PayloadChecksums; import com.yahoo.jrt.DataValue; import com.yahoo.jrt.Request; import com.yahoo.jrt.StringValue; @@ -11,7 +10,7 @@ import com.yahoo.jrt.Value; import com.yahoo.text.Utf8Array; import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.config.ErrorCode; -import com.yahoo.vespa.config.util.ConfigUtils; +import com.yahoo.vespa.config.PayloadChecksums; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -26,8 +25,7 @@ import static com.yahoo.vespa.config.PayloadChecksum.Type.XXHASH64; * The V3 config protocol implemented on the server side. The V3 protocol uses 2 fields: * * * A metadata field containing json data describing config generation, md5 and compression info - * * A data field containing compressed or uncompressed json config payload. This field can be empty if the payload - * has not changed since last request, triggering an optimization at the client where the previous payload is used instead. + * * A data field containing compressed or uncompressed json config payload * * The implementation of addOkResponse is optimized for doing as little copying of payload data as possible, ensuring * that we get a lower memory footprint. @@ -74,8 +72,6 @@ public class JRTServerConfigRequestV3 implements JRTServerConfigRequest { @Override public void addOkResponse(Payload payload, long generation, boolean applyOnRestart, PayloadChecksums payloadChecksums) { this.applyOnRestart = applyOnRestart; - boolean changedConfig = !payloadChecksums.equals(getRequestConfigChecksums()); - boolean changedConfigAndNewGeneration = changedConfig && ConfigUtils.isGenerationNewer(generation, getRequestGeneration()); Payload responsePayload = payload.withCompression(getCompressionType()); ByteArrayOutputStream byteArrayOutputStream = new NoCopyByteArrayOutputStream(4096); try { @@ -93,10 +89,6 @@ public class JRTServerConfigRequestV3 implements JRTServerConfigRequest { throw new RuntimeException("Payload is null for ' " + this + ", not able to create response"); } CompressionInfo compressionInfo = responsePayload.getCompressionInfo(); - // If payload is not being sent, we must adjust compression info to avoid client confusion. - if (!changedConfigAndNewGeneration) { - compressionInfo = CompressionInfo.create(compressionInfo.getCompressionType(), 0); - } compressionInfo.serialize(jsonGenerator); jsonGenerator.writeEndObject(); @@ -106,17 +98,13 @@ public class JRTServerConfigRequestV3 implements JRTServerConfigRequest { throw new IllegalArgumentException("Could not add OK response for " + this); } request.returnValues().add(createResponseValue(byteArrayOutputStream)); - if (changedConfigAndNewGeneration) { - ByteBuffer buf = responsePayload.getData().wrap(); - if (buf.hasArray() && buf.remaining() == buf.array().length) { - request.returnValues().add(new DataValue(buf.array())); - } else { - byte [] dst = new byte[buf.remaining()]; - buf.get(dst); - request.returnValues().add(new DataValue(dst)); - } + ByteBuffer buf = responsePayload.getData().wrap(); + if (buf.hasArray() && buf.remaining() == buf.array().length) { + request.returnValues().add(new DataValue(buf.array())); } else { - request.returnValues().add(new DataValue(new byte[0])); + byte[] dst = new byte[buf.remaining()]; + buf.get(dst); + request.returnValues().add(new DataValue(dst)); } } diff --git a/config/src/tests/configagent/configagent.cpp b/config/src/tests/configagent/configagent.cpp index d6766cce822..e4dc94fc2de 100644 --- a/config/src/tests/configagent/configagent.cpp +++ b/config/src/tests/configagent/configagent.cpp @@ -31,12 +31,12 @@ class MyConfigResponse : public ConfigResponse { public: MyConfigResponse(const ConfigKey & key, const ConfigValue & value, bool valid, int64_t timestamp, - const vespalib::string & md5, const std::string & errorMsg, int errorC0de, bool iserror) + const vespalib::string & xxhash64, const std::string & errorMsg, int errorC0de, bool iserror) : _key(key), _value(value), _fillCalled(false), _valid(valid), - _state(md5, timestamp, false), + _state(xxhash64, timestamp, false), _errorMessage(errorMsg), _errorCode(errorC0de), _isError(iserror) @@ -64,9 +64,9 @@ public: Trace _trace; - static ConfigResponse::UP createOKResponse(const ConfigKey & key, const ConfigValue & value, uint64_t timestamp = 10, const vespalib::string & md5 = "a") + static ConfigResponse::UP createOKResponse(const ConfigKey & key, const ConfigValue & value, uint64_t timestamp = 10, const vespalib::string & xxhash64 = "a") { - return std::make_unique<MyConfigResponse>(key, value, true, timestamp, md5, "", 0, false); + return std::make_unique<MyConfigResponse>(key, value, true, timestamp, xxhash64, "", 0, false); } static ConfigResponse::UP createServerErrorResponse(const ConfigKey & key, const ConfigValue & value) @@ -114,11 +114,11 @@ private: }; -ConfigValue createValue(const std::string & myField, const std::string & md5) +ConfigValue createValue(const std::string & myField, const std::string & xxhash64) { std::vector< vespalib::string > lines; lines.push_back("myField \"" + myField + "\""); - return ConfigValue(lines, md5); + return ConfigValue(lines, xxhash64); } static TimingValues testTimingValues( @@ -139,7 +139,7 @@ TEST("require that agent returns correct values") { ASSERT_EQUAL(500u, handler.getTimeout()); ASSERT_EQUAL(0u, handler.getWaitTime()); ConfigState cs; - ASSERT_EQUAL(cs.md5, handler.getConfigState().md5); + ASSERT_EQUAL(cs.xxhash64, handler.getConfigState().xxhash64); ASSERT_EQUAL(cs.generation, handler.getConfigState().generation); ASSERT_EQUAL(cs.applyOnRestart, handler.getConfigState().applyOnRestart); } @@ -167,7 +167,7 @@ TEST("require that important(the change) request is delivered to holder even if FRTConfigAgent handler(latch, testTimingValues); handler.handleResponse(MyConfigRequest(testKey), - MyConfigResponse::createOKResponse(testKey, testValue1, 1, testValue1.getMd5())); + MyConfigResponse::createOKResponse(testKey, testValue1, 1, testValue1.getXxhash64())); ASSERT_TRUE(latch->poll()); ConfigUpdate::UP update(latch->provide()); ASSERT_TRUE(update); @@ -176,9 +176,9 @@ TEST("require that important(the change) request is delivered to holder even if ASSERT_EQUAL("l33t", cfg.myField); handler.handleResponse(MyConfigRequest(testKey), - MyConfigResponse::createOKResponse(testKey, testValue2, 2, testValue2.getMd5())); + MyConfigResponse::createOKResponse(testKey, testValue2, 2, testValue2.getXxhash64())); handler.handleResponse(MyConfigRequest(testKey), - MyConfigResponse::createOKResponse(testKey, testValue2, 3, testValue2.getMd5())); + MyConfigResponse::createOKResponse(testKey, testValue2, 3, testValue2.getXxhash64())); ASSERT_TRUE(latch->poll()); update = latch->provide(); ASSERT_TRUE(update); diff --git a/config/src/tests/failover/failover.cpp b/config/src/tests/failover/failover.cpp index 2e039081716..cb8b7121b02 100644 --- a/config/src/tests/failover/failover.cpp +++ b/config/src/tests/failover/failover.cpp @@ -60,7 +60,7 @@ struct RPCServer : public FRT_Invokable { info.setString("uncompressedSize", "0"); root.setString(RESPONSE_CONFIGID, "myId"); root.setString(RESPONSE_CLIENT_HOSTNAME, "myhost"); - root.setString(RESPONSE_CONFIG_MD5, "md5"); + root.setString(RESPONSE_CONFIG_XXHASH64, "xxhash64"); root.setLong(RESPONSE_CONFIG_GENERATION, gen); root.setObject(RESPONSE_TRACE); Slime payload; diff --git a/config/src/tests/frt/frt.cpp b/config/src/tests/frt/frt.cpp index cb09b8f7254..5af02f22ed2 100644 --- a/config/src/tests/frt/frt.cpp +++ b/config/src/tests/frt/frt.cpp @@ -72,7 +72,7 @@ namespace { FRT_RPCRequest * createOKResponse(const vespalib::string & defName="", const vespalib::string & defMd5="", const vespalib::string & configId="", - const vespalib::string & configMd5="", + const vespalib::string & configXxhash64="", int changed=0, long generation=0, const std::vector<vespalib::string> & payload = std::vector<vespalib::string>(), @@ -85,7 +85,7 @@ namespace { ret.AddString(""); ret.AddString(defMd5.c_str()); ret.AddString(configId.c_str()); - ret.AddString(configMd5.c_str()); + ret.AddString(configXxhash64.c_str()); ret.AddInt32(changed); ret.AddInt64(generation); FRT_StringValue * payload_arr = ret.AddStringArray(payload.size()); @@ -268,16 +268,16 @@ TEST_FF("require that request is config task is scheduled", SourceFixture(), FRT TEST("require that v3 request is correctly initialized") { ConnectionMock conn; ConfigKey key = ConfigKey::create<MyConfig>("foobi"); - vespalib::string md5 = "mymd5"; + vespalib::string xxhash64 = "myxxhash64"; int64_t currentGeneration = 3; vespalib::string hostName = "myhost"; int64_t timeout = 3000; Trace traceIn(3); traceIn.trace(2, "Hei"); - FRTConfigRequestV3 v3req(&conn, key, md5, currentGeneration, hostName, + FRTConfigRequestV3 v3req(&conn, key, xxhash64, currentGeneration, hostName, timeout, traceIn, VespaVersion::fromString("1.2.3"), CompressionType::LZ4); - ASSERT_TRUE(v3req.verifyState(ConfigState(md5, 3, false))); - ASSERT_FALSE(v3req.verifyState(ConfigState(md5, 2, false))); + ASSERT_TRUE(v3req.verifyState(ConfigState(xxhash64, 3, false))); + ASSERT_FALSE(v3req.verifyState(ConfigState(xxhash64, 2, false))); ASSERT_FALSE(v3req.verifyState(ConfigState("xxx", 3, false))); ASSERT_FALSE(v3req.verifyState(ConfigState("xxx", 2, false))); @@ -297,7 +297,7 @@ TEST("require that v3 request is correctly initialized") { EXPECT_EQUAL(key.getConfigId(), root[REQUEST_CLIENT_CONFIGID].asString().make_string()); EXPECT_EQUAL(hostName, root[REQUEST_CLIENT_HOSTNAME].asString().make_string()); EXPECT_EQUAL(currentGeneration, root[REQUEST_CURRENT_GENERATION].asLong()); - EXPECT_EQUAL(md5, root[REQUEST_CONFIG_MD5].asString().make_string()); + EXPECT_EQUAL(xxhash64, root[REQUEST_CONFIG_XXHASH64].asString().make_string()); EXPECT_EQUAL(timeout, root[REQUEST_TIMEOUT].asLong()); EXPECT_EQUAL("LZ4", root[REQUEST_COMPRESSION_TYPE].asString().make_string()); EXPECT_EQUAL(root[REQUEST_VESPA_VERSION].asString().make_string(), "1.2.3"); @@ -322,7 +322,7 @@ struct V3RequestFixture { Cursor & root; FRT_RPCRequest * req; ConfigKey key; - vespalib::string md5; + vespalib::string xxhash64; int64_t generation; vespalib::string hostname; Trace traceIn; @@ -333,7 +333,7 @@ struct V3RequestFixture { root(slime.setObject()), req(conn.allocRPCRequest()), key(ConfigKey::create<BarConfig>("foobi")), - md5("mymd5"), + xxhash64("myxxhash64"), generation(3), hostname("myhhost"), traceIn(3) @@ -345,7 +345,7 @@ struct V3RequestFixture { root.setString(RESPONSE_DEF_MD5, Memory(key.getDefMd5())); root.setString(RESPONSE_CONFIGID, Memory(key.getConfigId())); root.setString(RESPONSE_CLIENT_HOSTNAME, Memory(hostname)); - root.setString(RESPONSE_CONFIG_MD5, Memory(md5)); + root.setString(RESPONSE_CONFIG_XXHASH64, Memory(xxhash64)); root.setLong(RESPONSE_CONFIG_GENERATION, generation); traceIn.serialize(root.setObject(RESPONSE_TRACE)); } @@ -379,7 +379,7 @@ struct V3RequestFixture { EXPECT_EQUAL(key.getConfigId(), responseKey.getConfigId()); EXPECT_EQUAL(hostname, response.getHostName()); ConfigState state(response.getConfigState()); - EXPECT_EQUAL(md5, state.md5); + EXPECT_EQUAL(xxhash64, state.xxhash64); EXPECT_EQUAL(generation, state.generation); ConfigValue value(response.getValue()); BarConfig::UP config(value.newInstance<BarConfig>()); diff --git a/config/src/tests/misc/misc.cpp b/config/src/tests/misc/misc.cpp index 1a8b8a59ede..55040242732 100644 --- a/config/src/tests/misc/misc.cpp +++ b/config/src/tests/misc/misc.cpp @@ -15,11 +15,11 @@ TEST("requireThatConfigUpdateWorks") { std::vector<vespalib::string> lines; lines.push_back("foo"); - ConfigUpdate up(ConfigValue(lines, "mymd5"), true, 1337); + ConfigUpdate up(ConfigValue(lines, "myxxhash"), true, 1337); ASSERT_EQUAL(1337, up.getGeneration()); ASSERT_TRUE(up.hasChanged()); - ConfigUpdate up2(ConfigValue(lines, "mymd52"), false, 1338); + ConfigUpdate up2(ConfigValue(lines, "myxxhash2"), false, 1338); ASSERT_EQUAL(1338, up2.getGeneration()); ASSERT_FALSE(up2.hasChanged()); } @@ -27,22 +27,22 @@ TEST("requireThatConfigUpdateWorks") { TEST("requireThatConfigValueWorks") { std::vector<vespalib::string> lines; lines.push_back("myFooField \"bar\""); - ConfigValue v1(lines, calculateContentMd5(lines)); - ConfigValue v2(lines, calculateContentMd5(lines)); - ConfigValue v3(lines, calculateContentMd5(lines)); + ConfigValue v1(lines, calculateContentXxhash64(lines)); + ConfigValue v2(lines, calculateContentXxhash64(lines)); + ConfigValue v3(lines, calculateContentXxhash64(lines)); lines.push_back("myFooField \"bar2\""); - ConfigValue v4(lines, calculateContentMd5(lines)); + ConfigValue v4(lines, calculateContentXxhash64(lines)); ASSERT_TRUE(v1 == v2); ASSERT_TRUE(v1 == v3); } TEST("requireThatConfigKeyWorks") { - ConfigKey key1("id1", "def1", "namespace1", "md51"); - ConfigKey key2("id1", "def1", "namespace1", "md51"); - ConfigKey key3("id2", "def1", "namespace1", "md51"); - ConfigKey key4("id1", "def2", "namespace1", "md51"); - ConfigKey key5("id1", "def1", "namespace2", "md51"); - ConfigKey key6("id1", "def1", "namespace1", "md52"); // Special case. Md5 does not matter, so should be qual to key1 and key2 + ConfigKey key1("id1", "def1", "namespace1", "xxhash1"); + ConfigKey key2("id1", "def1", "namespace1", "xxhash1"); + ConfigKey key3("id2", "def1", "namespace1", "xxhash1"); + ConfigKey key4("id1", "def2", "namespace1", "xxhash1"); + ConfigKey key5("id1", "def1", "namespace2", "xxhash1"); + ConfigKey key6("id1", "def1", "namespace1", "xxhash2"); // Special case. xxhash64 does not matter, so should be qual to key1 and key2 ASSERT_TRUE(key1 == key2); @@ -111,7 +111,7 @@ TEST("require that config key initializes schema") std::vector<vespalib::string> schema; schema.push_back("foo"); schema.push_back("bar"); - ConfigKey key("id1", "def1", "namespace1", "md51", schema); + ConfigKey key("id1", "def1", "namespace1", "xxhash1", schema); const std::vector<vespalib::string> &vref(key.getDefSchema()); for (size_t i = 0; i < schema.size(); i++) { ASSERT_EQUAL(schema[i], vref[i]); @@ -131,6 +131,7 @@ TEST("require that error codes are correctly translated to strings") { ASSERT_CONFIG(ILLEGAL_CONFIGID); ASSERT_CONFIG(ILLEGAL_DEF_MD5); ASSERT_CONFIG(ILLEGAL_CONFIG_MD5); + ASSERT_CONFIG(ILLEGAL_CONFIG_MD5); ASSERT_CONFIG(ILLEGAL_TIMEOUT); ASSERT_CONFIG(ILLEGAL_TIMESTAMP); ASSERT_CONFIG(ILLEGAL_NAME_SPACE); diff --git a/config/src/tests/subscriber/subscriber.cpp b/config/src/tests/subscriber/subscriber.cpp index d58699f26e0..5c5714a867a 100644 --- a/config/src/tests/subscriber/subscriber.cpp +++ b/config/src/tests/subscriber/subscriber.cpp @@ -20,7 +20,7 @@ namespace { { std::vector< vespalib::string > lines; lines.push_back(value); - return ConfigValue(lines, calculateContentMd5(lines)); + return ConfigValue(lines, calculateContentXxhash64(lines)); } ConfigValue createFooValue(const std::string & value) diff --git a/config/src/vespa/config/common/configstate.h b/config/src/vespa/config/common/configstate.h index 2dbea3cc30f..143d77e4ab9 100644 --- a/config/src/vespa/config/common/configstate.h +++ b/config/src/vespa/config/common/configstate.h @@ -13,17 +13,17 @@ struct ConfigState { public: ConfigState() - : md5(""), + : xxhash64(""), generation(0), applyOnRestart(false) { } - ConfigState(const vespalib::string & md5sum, int64_t gen, bool _applyOnRestart) - : md5(md5sum), + ConfigState(const vespalib::string & xxhash, int64_t gen, bool _applyOnRestart) + : xxhash64(xxhash), generation(gen), applyOnRestart(_applyOnRestart) { } - vespalib::string md5; + vespalib::string xxhash64; int64_t generation; bool applyOnRestart; @@ -32,7 +32,7 @@ public: } bool hasDifferentPayloadFrom(const ConfigState & other) const { - return (md5.compare(other.md5) != 0); + return (xxhash64.compare(other.xxhash64) != 0); } }; diff --git a/config/src/vespa/config/common/configvalue.cpp b/config/src/vespa/config/common/configvalue.cpp index d5c0c2047df..ce6fd3f20da 100644 --- a/config/src/vespa/config/common/configvalue.cpp +++ b/config/src/vespa/config/common/configvalue.cpp @@ -6,22 +6,22 @@ namespace config { -ConfigValue::ConfigValue(const std::vector<vespalib::string> & lines, const vespalib::string & md5sum) +ConfigValue::ConfigValue(const std::vector<vespalib::string> & lines, const vespalib::string & xxhash) : _payload(), _lines(lines), - _md5sum(md5sum) + _xxhash64(xxhash) { } ConfigValue::ConfigValue() : _payload(), _lines(), - _md5sum() + _xxhash64() { } -ConfigValue::ConfigValue(PayloadPtr payload, const vespalib::string & md5) +ConfigValue::ConfigValue(PayloadPtr payload, const vespalib::string & xxhash) : _payload(std::move(payload)), _lines(), - _md5sum(md5) + _xxhash64(xxhash) { } ConfigValue::ConfigValue(const ConfigValue &) = default; @@ -32,7 +32,7 @@ ConfigValue::~ConfigValue() = default; int ConfigValue::operator==(const ConfigValue & rhs) const { - return (_md5sum.compare(rhs._md5sum) == 0); + return (_xxhash64.compare(rhs._xxhash64) == 0); } int diff --git a/config/src/vespa/config/common/configvalue.h b/config/src/vespa/config/common/configvalue.h index a0450328f30..f5bfae00c19 100644 --- a/config/src/vespa/config/common/configvalue.h +++ b/config/src/vespa/config/common/configvalue.h @@ -21,8 +21,8 @@ typedef std::shared_ptr<const protocol::Payload> PayloadPtr; class ConfigValue { public: typedef std::unique_ptr<ConfigValue> UP; - ConfigValue(const std::vector<vespalib::string> & lines, const vespalib::string & md5sum); - ConfigValue(PayloadPtr data, const vespalib::string & md5sum); + ConfigValue(const std::vector<vespalib::string> & lines, const vespalib::string & xxhash); + ConfigValue(PayloadPtr data, const vespalib::string & xxhash); ConfigValue(); ConfigValue(const ConfigValue &); ConfigValue & operator = (const ConfigValue &); @@ -36,7 +36,7 @@ public: const std::vector<vespalib::string> & getLines() const { return _lines; } std::vector<vespalib::string> getLegacyFormat() const; const vespalib::string asJson() const; - const vespalib::string getMd5() const { return _md5sum; } + const vespalib::string getXxhash64() const { return _xxhash64; } void serializeV1(::vespalib::slime::Cursor & cursor) const; void serializeV2(::vespalib::slime::Cursor & cursor) const; @@ -47,7 +47,7 @@ public: private: PayloadPtr _payload; std::vector<vespalib::string> _lines; - vespalib::string _md5sum; + vespalib::string _xxhash64; }; } //namespace config diff --git a/config/src/vespa/config/common/misc.cpp b/config/src/vespa/config/common/misc.cpp index 1040962e25c..e1b4390caf5 100644 --- a/config/src/vespa/config/common/misc.cpp +++ b/config/src/vespa/config/common/misc.cpp @@ -1,7 +1,9 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "misc.h" -#include <vespa/vespalib/util/md5.h> +#include <iostream> +#include <sstream> +#include <xxhash.h> #include <vespa/vespalib/stllike/asciistream.h> #include <vespa/vespalib/util/exceptions.h> #include <vespa/vespalib/data/slime/slime.h> @@ -12,12 +14,12 @@ using vespalib::Memory; namespace config { vespalib::string -calculateContentMd5(const std::vector<vespalib::string> & fileContents) +calculateContentXxhash64(const std::vector<vespalib::string> & fileContents) { vespalib::string normalizedLines; - int compact_md5size = 16; - unsigned char md5sum[compact_md5size]; + XXH64_hash_t xxhash64; vespalib::asciistream s; + std::stringstream ss; // remove comments, trailing spaces and empty lines // TODO: Remove multiple spaces and space before comma, like in Java @@ -30,16 +32,11 @@ calculateContentMd5(const std::vector<vespalib::string> & fileContents) normalizedLines += line; } } - fastc_md5sum((const unsigned char*)normalizedLines.c_str(), normalizedLines.size(), md5sum); + xxhash64 = XXH64((const unsigned char*)normalizedLines.c_str(), normalizedLines.size(), 0); - // convert to 32 character hex string - for (int i = 0; i < compact_md5size; i++) { - if (md5sum[i] < 16) { - s << "0"; - } - s << vespalib::hex << (int)md5sum[i]; - } - return s.str(); + ss << std::hex << xxhash64; + ss << std::endl; + return ss.str(); } bool diff --git a/config/src/vespa/config/common/misc.h b/config/src/vespa/config/common/misc.h index 0299ef001f1..089fd890224 100644 --- a/config/src/vespa/config/common/misc.h +++ b/config/src/vespa/config/common/misc.h @@ -19,7 +19,7 @@ namespace config { /** * Miscellaneous utility functions specific to config. */ -vespalib::string calculateContentMd5(const std::vector<vespalib::string> & fileContents); +vespalib::string calculateContentXxhash64(const std::vector<vespalib::string> & fileContents); bool isGenerationNewer(int64_t newGen, int64_t oldGen); diff --git a/config/src/vespa/config/file/filesource.cpp b/config/src/vespa/config/file/filesource.cpp index fcc69f68066..8e25d5ccffc 100644 --- a/config/src/vespa/config/file/filesource.cpp +++ b/config/src/vespa/config/file/filesource.cpp @@ -27,10 +27,10 @@ FileSource::getConfig() int64_t last = getLast(_fileName); if (last > _lastLoaded) { - _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, calculateContentMd5(lines)), true, _generation))); + _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, calculateContentXxhash64(lines)), true, _generation))); _lastLoaded = last; } else { - _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, calculateContentMd5(lines)), false, _generation))); + _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, calculateContentXxhash64(lines)), false, _generation))); } } diff --git a/config/src/vespa/config/frt/frtconfigagent.cpp b/config/src/vespa/config/frt/frtconfigagent.cpp index 0e5ee4f6b05..30112b677e1 100644 --- a/config/src/vespa/config/frt/frtconfigagent.cpp +++ b/config/src/vespa/config/frt/frtconfigagent.cpp @@ -27,7 +27,7 @@ FRTConfigAgent::handleResponse(const ConfigRequest & request, ConfigResponse::UP { if (LOG_WOULD_LOG(spam)) { const ConfigKey & key(request.getKey()); - LOG(spam, "current state for %s: generation %" PRId64 " md5 %s", key.toString().c_str(), _configState.generation, _configState.md5.c_str()); + LOG(spam, "current state for %s: generation %" PRId64 " xxhash64 %s", key.toString().c_str(), _configState.generation, _configState.xxhash64.c_str()); } if (response->validateResponse() && !response->isError()) { handleOKResponse(request, std::move(response)); @@ -57,12 +57,12 @@ void FRTConfigAgent::handleUpdatedGeneration(const ConfigKey & key, const ConfigState & newState, const ConfigValue & configValue) { if (LOG_WOULD_LOG(spam)) { - LOG(spam, "new generation %" PRId64 " md5:%s for key %s", newState.generation, newState.md5.c_str(), key.toString().c_str()); - LOG(spam, "Old config: md5:%s \n%s", _latest.getMd5().c_str(), _latest.asJson().c_str()); - LOG(spam, "New config: md5:%s \n%s", configValue.getMd5().c_str(), configValue.asJson().c_str()); + LOG(spam, "new generation %" PRId64 " xxhash64:%s for key %s", newState.generation, newState.xxhash64.c_str(), key.toString().c_str()); + LOG(spam, "Old config: xxhash64:%s \n%s", _latest.getXxhash64().c_str(), _latest.asJson().c_str()); + LOG(spam, "New config: xxhash64:%s \n%s", configValue.getXxhash64().c_str(), configValue.asJson().c_str()); } bool changed = false; - if (_latest.getMd5() != configValue.getMd5()) { + if (_latest.getXxhash64() != configValue.getXxhash64()) { _latest = configValue; changed = true; } diff --git a/config/src/vespa/config/frt/frtconfigrequestfactory.cpp b/config/src/vespa/config/frt/frtconfigrequestfactory.cpp index fbc13556d14..3573b8c9cfe 100644 --- a/config/src/vespa/config/frt/frtconfigrequestfactory.cpp +++ b/config/src/vespa/config/frt/frtconfigrequestfactory.cpp @@ -24,7 +24,7 @@ FRTConfigRequest::UP FRTConfigRequestFactory::createConfigRequest(const ConfigKey & key, Connection * connection, const ConfigState & state, int64_t serverTimeout) const { - return make_unique<FRTConfigRequestV3>(connection, key, state.md5, state.generation, _hostName, + return make_unique<FRTConfigRequestV3>(connection, key, state.xxhash64, state.generation, _hostName, serverTimeout, Trace(_traceLevel), _vespaVersion, _compressionType); } diff --git a/config/src/vespa/config/frt/frtconfigresponsev3.cpp b/config/src/vespa/config/frt/frtconfigresponsev3.cpp index 80cdf88a79a..351f0fc8136 100644 --- a/config/src/vespa/config/frt/frtconfigresponsev3.cpp +++ b/config/src/vespa/config/frt/frtconfigresponsev3.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "frtconfigresponsev3.h" #include "compressioninfo.h" #include <vespa/fnet/frt/values.h> @@ -53,7 +53,7 @@ FRTConfigResponseV3::getResponseTypes() const ConfigValue FRTConfigResponseV3::readConfigValue() const { - vespalib::string md5(_data->get()[RESPONSE_CONFIG_MD5].asString().make_string()); + vespalib::string xxhash64(_data->get()[RESPONSE_CONFIG_XXHASH64].asString().make_string()); CompressionInfo info; info.deserialize(_data->get()[RESPONSE_COMPRESSION_INFO]); auto slime = std::make_unique<Slime>(); @@ -67,9 +67,9 @@ FRTConfigResponseV3::readConfigValue() const } } if (LOG_WOULD_LOG(spam)) { - LOG(spam, "read config value md5(%s), payload size: %lu", md5.c_str(), data.memRef.size); + LOG(spam, "read config value xxhash64(%s), payload size: %lu", xxhash64.c_str(), data.memRef.size); } - return ConfigValue(std::make_shared<V3Payload>(std::move(slime)), md5); + return ConfigValue(std::make_shared<V3Payload>(std::move(slime)), xxhash64); } } // namespace config diff --git a/config/src/vespa/config/frt/protocol.cpp b/config/src/vespa/config/frt/protocol.cpp index 4a1b0a6ddef..c6c99ccf1ff 100644 --- a/config/src/vespa/config/frt/protocol.cpp +++ b/config/src/vespa/config/frt/protocol.cpp @@ -25,6 +25,7 @@ const Memory REQUEST_DEF_CONTENT = "defContent"; const Memory REQUEST_CLIENT_CONFIGID = "configId"; const Memory REQUEST_CLIENT_HOSTNAME = "clientHostname"; const Memory REQUEST_CONFIG_MD5 = "configMD5"; +const Memory REQUEST_CONFIG_XXHASH64 = "configXxhash64"; const Memory REQUEST_CURRENT_GENERATION = "currentGeneration"; const Memory REQUEST_TIMEOUT = "timeout"; const Memory REQUEST_TRACE = "trace"; @@ -36,7 +37,7 @@ const Memory RESPONSE_DEF_NAMESPACE = "defNamespace"; const Memory RESPONSE_DEF_MD5 = "defMD5"; const Memory RESPONSE_CONFIGID = "configId"; const Memory RESPONSE_CLIENT_HOSTNAME = "clientHostname"; -const Memory RESPONSE_CONFIG_MD5 = "configMD5"; +const Memory RESPONSE_CONFIG_XXHASH64 = "configXxhash64"; const Memory RESPONSE_CONFIG_GENERATION = "generation"; const Memory RESPONSE_PAYLOAD = "payload"; const Memory RESPONSE_TRACE = "trace"; diff --git a/config/src/vespa/config/frt/protocol.h b/config/src/vespa/config/frt/protocol.h index 0ec16952701..ce09217b619 100644 --- a/config/src/vespa/config/frt/protocol.h +++ b/config/src/vespa/config/frt/protocol.h @@ -36,7 +36,7 @@ extern const vespalib::Memory REQUEST_DEF_MD5; extern const vespalib::Memory REQUEST_DEF_CONTENT; extern const vespalib::Memory REQUEST_CLIENT_CONFIGID; extern const vespalib::Memory REQUEST_CLIENT_HOSTNAME; -extern const vespalib::Memory REQUEST_CONFIG_MD5; +extern const vespalib::Memory REQUEST_CONFIG_XXHASH64; extern const vespalib::Memory REQUEST_CURRENT_GENERATION; extern const vespalib::Memory REQUEST_TIMEOUT; extern const vespalib::Memory REQUEST_TRACE; @@ -48,7 +48,7 @@ extern const vespalib::Memory RESPONSE_DEF_NAMESPACE; extern const vespalib::Memory RESPONSE_DEF_MD5; extern const vespalib::Memory RESPONSE_CONFIGID; extern const vespalib::Memory RESPONSE_CLIENT_HOSTNAME; -extern const vespalib::Memory RESPONSE_CONFIG_MD5; +extern const vespalib::Memory RESPONSE_CONFIG_XXHASH64; extern const vespalib::Memory RESPONSE_CONFIG_GENERATION; extern const vespalib::Memory RESPONSE_PAYLOAD; extern const vespalib::Memory RESPONSE_TRACE; diff --git a/config/src/vespa/config/frt/slimeconfigrequest.cpp b/config/src/vespa/config/frt/slimeconfigrequest.cpp index 8a6706974f6..3573f8c07b9 100644 --- a/config/src/vespa/config/frt/slimeconfigrequest.cpp +++ b/config/src/vespa/config/frt/slimeconfigrequest.cpp @@ -23,7 +23,7 @@ const vespalib::string SlimeConfigRequest::REQUEST_TYPES = "s"; SlimeConfigRequest::SlimeConfigRequest(Connection * connection, const ConfigKey & key, - const vespalib::string & configMd5, + const vespalib::string & configXxhash64, int64_t currentGeneration, const vespalib::string & hostName, int64_t serverTimeout, @@ -35,7 +35,7 @@ SlimeConfigRequest::SlimeConfigRequest(Connection * connection, : FRTConfigRequest(connection, key), _data() { - populateSlimeRequest(key, configMd5, currentGeneration, hostName, serverTimeout, trace, vespaVersion, protocolVersion, compressionType); + populateSlimeRequest(key, configXxhash64, currentGeneration, hostName, serverTimeout, trace, vespaVersion, protocolVersion, compressionType); _request->SetMethodName(methodName.c_str()); _parameters.AddString(createJsonFromSlime(_data).c_str()); } @@ -43,13 +43,13 @@ SlimeConfigRequest::SlimeConfigRequest(Connection * connection, bool SlimeConfigRequest::verifyState(const ConfigState & state) const { - return (state.md5.compare(_data[REQUEST_CONFIG_MD5].asString().make_stringref()) == 0 && + return (state.xxhash64.compare(_data[REQUEST_CONFIG_XXHASH64].asString().make_stringref()) == 0 && state.generation == _data[REQUEST_CURRENT_GENERATION].asLong()); } void SlimeConfigRequest::populateSlimeRequest(const ConfigKey & key, - const vespalib::string & configMd5, + const vespalib::string & configXxhash64, int64_t currentGeneration, const vespalib::string & hostName, int64_t serverTimeout, @@ -67,7 +67,7 @@ SlimeConfigRequest::populateSlimeRequest(const ConfigKey & key, def.serialize(root.setArray(REQUEST_DEF_CONTENT)); root.setString(REQUEST_CLIENT_CONFIGID, Memory(key.getConfigId())); root.setString(REQUEST_CLIENT_HOSTNAME, Memory(hostName)); - root.setString(REQUEST_CONFIG_MD5, Memory(configMd5)); + root.setString(REQUEST_CONFIG_XXHASH64, Memory(configXxhash64)); root.setLong(REQUEST_CURRENT_GENERATION, currentGeneration); root.setLong(REQUEST_TIMEOUT, serverTimeout); trace.serialize(root.setObject(REQUEST_TRACE)); diff --git a/config/src/vespa/config/frt/slimeconfigrequest.h b/config/src/vespa/config/frt/slimeconfigrequest.h index 6f2f42c98d5..d25530f815c 100644 --- a/config/src/vespa/config/frt/slimeconfigrequest.h +++ b/config/src/vespa/config/frt/slimeconfigrequest.h @@ -19,7 +19,7 @@ class SlimeConfigRequest : public FRTConfigRequest { public: SlimeConfigRequest(Connection * connection, const ConfigKey & key, - const vespalib::string & configMd5, + const vespalib::string & configXxhash64, int64_t currentGeneration, const vespalib::string & hostName, int64_t serverTimeout, @@ -33,7 +33,7 @@ public: virtual ConfigResponse::UP createResponse(FRT_RPCRequest * request) const override = 0; private: void populateSlimeRequest(const ConfigKey & key, - const vespalib::string & configMd5, + const vespalib::string & configXxhash64, int64_t currentGeneration, const vespalib::string & hostName, int64_t serverTimeout, diff --git a/config/src/vespa/config/frt/slimeconfigresponse.cpp b/config/src/vespa/config/frt/slimeconfigresponse.cpp index af224008d01..c1f3df7f674 100644 --- a/config/src/vespa/config/frt/slimeconfigresponse.cpp +++ b/config/src/vespa/config/frt/slimeconfigresponse.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "slimeconfigresponse.h" #include <vespa/config/common/misc.h> #include <vespa/fnet/frt/values.h> @@ -66,7 +66,7 @@ ConfigState SlimeConfigResponse::readState() const { const Slime & data(*_data); - return ConfigState(data.get()[RESPONSE_CONFIG_MD5].asString().make_string(), + return ConfigState(data.get()[RESPONSE_CONFIG_XXHASH64].asString().make_string(), data.get()[RESPONSE_CONFIG_GENERATION].asLong(), data.get()[RESPONSE_APPLY_ON_RESTART].asBool()); } diff --git a/config/src/vespa/config/print/asciiconfigreader.hpp b/config/src/vespa/config/print/asciiconfigreader.hpp index b4a25f4fbbd..9d142e83c00 100644 --- a/config/src/vespa/config/print/asciiconfigreader.hpp +++ b/config/src/vespa/config/print/asciiconfigreader.hpp @@ -27,7 +27,7 @@ AsciiConfigReader<ConfigType>::read() while (getline(_is, line)) { lines.push_back(line); } - return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentMd5(lines)))); + return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentXxhash64(lines)))); } } // namespace config diff --git a/config/src/vespa/config/print/fileconfigreader.hpp b/config/src/vespa/config/print/fileconfigreader.hpp index bfebb4b89b9..87a4e20866c 100644 --- a/config/src/vespa/config/print/fileconfigreader.hpp +++ b/config/src/vespa/config/print/fileconfigreader.hpp @@ -44,7 +44,7 @@ FileConfigReader<ConfigType>::read() for (std::getline(f, line); f; std::getline(f, line)) { lines.push_back(line); } - return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentMd5(lines)))); + return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentXxhash64(lines)))); } } // namespace config diff --git a/config/src/vespa/config/print/istreamconfigreader.hpp b/config/src/vespa/config/print/istreamconfigreader.hpp index 70f7b7e5f28..3a501d26245 100644 --- a/config/src/vespa/config/print/istreamconfigreader.hpp +++ b/config/src/vespa/config/print/istreamconfigreader.hpp @@ -31,7 +31,7 @@ IstreamConfigReader<ConfigType>::read() while (getline(_is, line)) { lines.push_back(line); } - return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentMd5(lines)))); + return std::unique_ptr<ConfigType>(new ConfigType(ConfigValue(lines, calculateContentXxhash64(lines)))); } } // namespace config diff --git a/config/src/vespa/config/raw/rawsource.cpp b/config/src/vespa/config/raw/rawsource.cpp index 2b38cff214c..a6c4da0b4dd 100644 --- a/config/src/vespa/config/raw/rawsource.cpp +++ b/config/src/vespa/config/raw/rawsource.cpp @@ -16,7 +16,7 @@ void RawSource::getConfig() { auto lines(readConfig()); - ConfigValue value(lines, calculateContentMd5(lines)); + ConfigValue value(lines, calculateContentXxhash64(lines)); _holder->handle(ConfigUpdate::UP(new ConfigUpdate(value, true, 1))); } diff --git a/config/src/vespa/config/retriever/configsnapshot.cpp b/config/src/vespa/config/retriever/configsnapshot.cpp index 7aaf4ffd6f8..35f4415a26e 100644 --- a/config/src/vespa/config/retriever/configsnapshot.cpp +++ b/config/src/vespa/config/retriever/configsnapshot.cpp @@ -150,7 +150,7 @@ void ConfigSnapshot::serializeValueV2(Cursor & cursor, const Value & value) const { cursor.setDouble("lastChanged", value.first); - cursor.setString("md5", Memory(value.second.getMd5())); + cursor.setString("xxhash64", Memory(value.second.getXxhash64())); value.second.serializeV2(cursor.setObject("payload")); } @@ -225,7 +225,7 @@ ConfigSnapshot::deserializeValueV1(Inspector & inspector) const for (size_t i = 0; i < s.children(); i++) { payload.push_back(s[i].asString().make_string()); } - return Value(lastChanged, ConfigValue(payload, calculateContentMd5(payload))); + return Value(lastChanged, ConfigValue(payload, calculateContentXxhash64(payload))); } namespace { @@ -247,10 +247,10 @@ std::pair<int64_t, ConfigValue> ConfigSnapshot::deserializeValueV2(Inspector & inspector) const { int64_t lastChanged = static_cast<int64_t>(inspector["lastChanged"].asDouble()); - vespalib::string md5(inspector["md5"].asString().make_string()); + vespalib::string xxhash64(inspector["xxhash64"].asString().make_string()); auto payload = std::make_unique<FixedPayload>(); copySlimeObject(inspector["payload"], payload->getData().setObject()); - return Value(lastChanged, ConfigValue(std::move(payload) , md5)); + return Value(lastChanged, ConfigValue(std::move(payload), xxhash64)); } } diff --git a/config/src/vespa/config/set/configinstancesourcefactory.cpp b/config/src/vespa/config/set/configinstancesourcefactory.cpp index 7f963847997..5ebffd4c4e9 100644 --- a/config/src/vespa/config/set/configinstancesourcefactory.cpp +++ b/config/src/vespa/config/set/configinstancesourcefactory.cpp @@ -14,8 +14,8 @@ public: void close() override { } void getConfig() override { std::vector<vespalib::string> lines(_buffer.getlines()); - std::string currentMd5(config::calculateContentMd5(lines)); - _holder->handle(config::ConfigUpdate::UP(new config::ConfigUpdate(config::ConfigValue(lines, currentMd5), true, _generation))); + std::string currentXxhash64(config::calculateContentXxhash64(lines)); + _holder->handle(config::ConfigUpdate::UP(new config::ConfigUpdate(config::ConfigValue(lines, currentXxhash64), true, _generation))); } void reload(int64_t generation) override { _generation = generation; } diff --git a/config/src/vespa/config/set/configsetsource.cpp b/config/src/vespa/config/set/configsetsource.cpp index e6ab890a9df..36c7d70e0d3 100644 --- a/config/src/vespa/config/set/configsetsource.cpp +++ b/config/src/vespa/config/set/configsetsource.cpp @@ -30,16 +30,16 @@ ConfigSetSource::getConfig() AsciiConfigWriter writer(ss); writer.write(*instance); std::vector<vespalib::string> lines(ss.getlines()); - std::string currentMd5(calculateContentMd5(lines)); + std::string currentXxhash64(calculateContentXxhash64(lines)); - if (isGenerationNewer(_generation, _lastState.generation) && currentMd5.compare(_lastState.md5) != 0) { + if (isGenerationNewer(_generation, _lastState.generation) && currentXxhash64.compare(_lastState.xxhash64) != 0) { LOG(debug, "New generation, updating"); - _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, currentMd5), true, _generation))); - _lastState.md5 = currentMd5; + _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, currentXxhash64), true, _generation))); + _lastState.xxhash64 = currentXxhash64; _lastState.generation = _generation; } else { LOG(debug, "Sending timestamp update"); - _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, currentMd5), false, _generation))); + _holder->handle(ConfigUpdate::UP(new ConfigUpdate(ConfigValue(lines, currentXxhash64), false, _generation))); _lastState.generation = _generation; } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java index 89987891c61..8dedb5a29c9 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java @@ -187,8 +187,9 @@ public class ModelContextImpl implements ModelContext { private final double resourceLimitMemory; private final double minNodeRatioPerGroup; private final int metricsproxyNumThreads; - private final boolean enforceRankProfileInheritance; private final boolean newLocationBrokerLogic; + private final boolean containerDumpHeapOnShutdownTimeout; + private final double containerShutdownTimeout; public FeatureFlags(FlagSource source, ApplicationId appId) { this.defaultTermwiseLimit = flagValue(source, appId, Flags.DEFAULT_TERM_WISE_LIMIT); @@ -214,8 +215,9 @@ public class ModelContextImpl implements ModelContext { this.resourceLimitMemory = flagValue(source, appId, PermanentFlags.RESOURCE_LIMIT_MEMORY); this.minNodeRatioPerGroup = flagValue(source, appId, Flags.MIN_NODE_RATIO_PER_GROUP); this.metricsproxyNumThreads = flagValue(source, appId, Flags.METRICSPROXY_NUM_THREADS); - this.enforceRankProfileInheritance = flagValue(source, appId, Flags.ENFORCE_RANK_PROFILE_INHERITANCE); this.newLocationBrokerLogic = flagValue(source, appId, Flags.NEW_LOCATION_BROKER_LOGIC); + this.containerDumpHeapOnShutdownTimeout = flagValue(source, appId, Flags.CONTAINER_DUMP_HEAP_ON_SHUTDOWN_TIMEOUT); + this.containerShutdownTimeout = flagValue(source, appId,Flags.CONTAINER_SHUTDOWN_TIMEOUT); } @Override public double defaultTermwiseLimit() { return defaultTermwiseLimit; } @@ -245,8 +247,8 @@ public class ModelContextImpl implements ModelContext { @Override public double minNodeRatioPerGroup() { return minNodeRatioPerGroup; } @Override public int metricsproxyNumThreads() { return metricsproxyNumThreads; } @Override public boolean newLocationBrokerLogic() { return newLocationBrokerLogic; } - - @Override public boolean enforceRankProfileInheritance() { return enforceRankProfileInheritance; } + @Override public double containerShutdownTimeout() { return containerShutdownTimeout; } + @Override public boolean containerDumpHeapOnShutdownTimeout() { return containerDumpHeapOnShutdownTimeout; } private static <V> V flagValue(FlagSource source, ApplicationId appId, UnboundFlag<? extends V, ?, ?> flag) { return flag.bindTo(source) diff --git a/configserver/src/main/sh/start-configserver b/configserver/src/main/sh/start-configserver index f9eb5e3a0a5..965dc8756be 100755 --- a/configserver/src/main/sh/start-configserver +++ b/configserver/src/main/sh/start-configserver @@ -188,7 +188,6 @@ vespa-run-as-vespa-user vespa-runserver -s configserver -r 30 -p $pidfile -- \ -Djdisc.config.file=$cfpfile \ -Djdisc.export.packages= \ -Djdisc.cache.path=$bundlecachedir \ - -Djdisc.debug.resources=false \ -Djdisc.bundle.path=${VESPA_HOME}/lib/jars \ -Djdisc.logger.enabled=true \ -Djdisc.logger.level=ALL \ diff --git a/container-core/src/main/resources/configdefinitions/container.qr.def b/container-core/src/main/resources/configdefinitions/container.qr.def index 9d9b84eb428..08a598bf4bf 100644 --- a/container-core/src/main/resources/configdefinitions/container.qr.def +++ b/container-core/src/main/resources/configdefinitions/container.qr.def @@ -30,3 +30,9 @@ nodeIndex int default=0 ## Force restart of container on deploy, and defer any changes until restart restartOnDeploy bool default=false restart + +## Force heapdump if process is not able to stop within shutdown.timeout +shutdown.dumpHeapOnTimeout bool default=false + +## Timeout for clean shutdown +shutdown.timeout double default=50.0 diff --git a/container-core/src/main/sh/find-pid b/container-core/src/main/sh/find-pid index e1c2195c805..56c387387d4 100755 --- a/container-core/src/main/sh/find-pid +++ b/container-core/src/main/sh/find-pid @@ -77,20 +77,26 @@ findhost set -euo pipefail if (( $# != 1 )); then - echo "Usage: $0 <service-config-id>" >&2 + echo "Usage: $0 <service-name-or-config-id>" >&2 exit 1 fi -readonly config_id=$1 -status=$(vespa-sentinel-cmd list 2>/dev/null | grep "id=\"${config_id}\"") -if [ -z "${status}" ]; then - echo "No service named '${config_id}'" >&2 - exit 1 -fi -readonly pid=$(echo ${status} | cut -d " " -f 4 | cut -d "=" -f 2) -if ! [[ "${pid}" =~ ^[0-9]+$ ]]; then - echo "Could not find valid pid for '${config_id}' (pid='${pid}')" >&2 - exit 1 +readonly service=$1 +readonly pid_file="$VESPA_HOME/var/run/$service.pid" +if [ -f "$pid_file" ]; then + parent_pid=$(cat "$pid_file") + pid=$(pgrep --parent $parent_pid) +else + status=$(vespa-sentinel-cmd list 2>/dev/null | grep --perl-regexp "($service state=|id=\"$service\")") + if [ -z "${status}" ]; then + echo "No service with name or config id '${service}'" >&2 + exit 1 + fi + pid=$(echo ${status} | cut -d " " -f 4 | cut -d "=" -f 2) + if ! [[ "${pid}" =~ ^[0-9]+$ ]]; then + echo "Could not find valid pid for '${service}' (pid='${pid}')" >&2 + exit 1 + fi fi if [ -z "$(ps -p ${pid} -o pid=)" ]; then echo "Could not find process for '${pid}'" >&2 diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java index 853224a5b91..a84d2521b8b 100644 --- a/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java +++ b/container-disc/src/main/java/com/yahoo/container/jdisc/ConfiguredApplication.java @@ -1,10 +1,12 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.container.jdisc; +import com.google.common.util.concurrent.AtomicDouble; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Injector; import com.yahoo.cloud.config.SlobroksConfig; +import com.yahoo.component.Vtag; import com.yahoo.component.provider.ComponentRegistry; import com.yahoo.concurrent.DaemonThreadFactory; import com.yahoo.config.ConfigInstance; @@ -43,7 +45,6 @@ import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.defaults.Defaults; import com.yahoo.yolean.Exceptions; -import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.IdentityHashMap; @@ -54,6 +55,7 @@ import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -74,6 +76,8 @@ public final class ConfiguredApplication implements Application { private final String configId; private final OsgiFramework osgiFramework; private final com.yahoo.jdisc.Timer timerSingleton; + private final AtomicBoolean dumpHeapOnShutdownTimeout = new AtomicBoolean(false); + private final AtomicDouble shudownTimeoutS = new AtomicDouble(50.0); // Subscriber that is used when this is not a standalone-container. Subscribes // to config to make sure that container will be registered in slobrok (by {@link com.yahoo.jrt.slobrok.api.Register}) // if slobrok config changes (typically slobroks moving to other nodes) @@ -99,7 +103,7 @@ public final class ConfiguredApplication implements Application { static { LogSetup.initVespaLogging("Container"); - log.log(Level.INFO, "Starting container"); + log.log(Level.INFO, "Starting jdisc" + (Vtag.currentVersion.isEmpty() ? "" : " at version " + Vtag.currentVersion)); } /** @@ -133,7 +137,7 @@ public final class ConfiguredApplication implements Application { @Override public void start() { qrConfig = getConfig(QrConfig.class, true); - + reconfigure(qrConfig); hackToInitializeServer(qrConfig); ContainerBuilder builder = createBuilderWithGuiceBindings(); @@ -222,6 +226,7 @@ public final class ConfiguredApplication implements Application { while (true) { subscriber.waitNextGeneration(false); QrConfig newConfig = QrConfig.class.cast(first(subscriber.config().values())); + reconfigure(qrConfig); if (qrConfig.rpc().port() != newConfig.rpc().port()) { com.yahoo.protect.Process.logAndDie( "Rpc port config has changed from " + @@ -235,6 +240,11 @@ public final class ConfiguredApplication implements Application { } } + void reconfigure(QrConfig qrConfig) { + dumpHeapOnShutdownTimeout.set(qrConfig.shutdown().dumpHeapOnTimeout()); + shudownTimeoutS.set(qrConfig.shutdown().timeout()); + } + private void initializeAndActivateContainer(ContainerBuilder builder) { addHandlerBindings(builder, Container.get().getRequestHandlerRegistry(), configurer.getComponent(ApplicationContext.class).discBindingsConfig); @@ -401,13 +411,11 @@ public final class ConfiguredApplication implements Application { private void startShutdownDeadlineExecutor() { shutdownDeadlineExecutor = new ScheduledThreadPoolExecutor(1, new DaemonThreadFactory("Shutdown deadline timer")); shutdownDeadlineExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); - long delayMillis = 50 * 1000; + long delayMillis = (long)(shudownTimeoutS.get() * 1000.0); shutdownDeadlineExecutor.schedule(() -> { - String heapDumpName = Defaults.getDefaults().underVespaHome("var/crash/java_pid.") + ProcessHandle.current().pid() + ".hprof"; - try { + if (dumpHeapOnShutdownTimeout.get()) { + String heapDumpName = Defaults.getDefaults().underVespaHome("var/crash/java_pid.") + ProcessHandle.current().pid() + ".hprof"; com.yahoo.protect.Process.dumpHeap(heapDumpName, true); - } catch (IOException e) { - log.log(Level.WARNING, "Failed writing heap dump:", e); } com.yahoo.protect.Process.logAndDie( "Timed out waiting for application shutdown. Please check that all your request handlers " + diff --git a/container-disc/src/main/sh/vespa-start-container-daemon.sh b/container-disc/src/main/sh/vespa-start-container-daemon.sh index f09a17de5e5..2d9bbcf21fc 100755 --- a/container-disc/src/main/sh/vespa-start-container-daemon.sh +++ b/container-disc/src/main/sh/vespa-start-container-daemon.sh @@ -212,7 +212,6 @@ exec $numactlcmd $envcmd java \ -Djdisc.config.file="$cfpfile" \ -Djdisc.export.packages=${jdisc_export_packages} \ -Djdisc.cache.path="$bundlecachedir" \ - -Djdisc.debug.resources=false \ -Djdisc.bundle.path="${VESPA_HOME}/lib/jars" \ -Djdisc.logger.enabled=false \ -Djdisc.logger.level=ALL \ diff --git a/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/NetworkMultiplexerHolder.java b/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/NetworkMultiplexerHolder.java index 89ecc931efb..48657de1910 100644 --- a/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/NetworkMultiplexerHolder.java +++ b/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/NetworkMultiplexerHolder.java @@ -39,7 +39,7 @@ public class NetworkMultiplexerHolder extends AbstractComponent { public void deconstruct() { synchronized (monitor) { if (net != null) { - net.destroy(); + net.disown(); net = null; } destroyed = true; diff --git a/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusClient.java b/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusClient.java index 922e4140868..009d1619503 100644 --- a/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusClient.java +++ b/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusClient.java @@ -9,6 +9,8 @@ import com.yahoo.jdisc.handler.ContentChannel; import com.yahoo.jdisc.handler.RequestDeniedException; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.service.ClientProvider; + +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import com.yahoo.messagebus.EmptyReply; import com.yahoo.messagebus.Error; @@ -29,9 +31,10 @@ import java.util.logging.Logger; public final class MbusClient extends AbstractResource implements ClientProvider, ReplyHandler { private static final Logger log = Logger.getLogger(MbusClient.class.getName()); + private static final AtomicInteger threadId = new AtomicInteger(0); private final BlockingQueue<MbusRequest> queue = new LinkedBlockingQueue<>(); private final ClientSession session; - private final Thread thread = new Thread(new SenderTask(), "MbusClient"); + private final Thread thread; private volatile boolean done = false; private final ResourceReference sessionReference; @@ -39,6 +42,7 @@ public final class MbusClient extends AbstractResource implements ClientProvider public MbusClient(ClientSession session) { this.session = session; this.sessionReference = session.refer(); + thread = new Thread(new SenderTask(), "mbus-client-" + threadId.getAndIncrement()); } @Override diff --git a/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java b/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java index 67badddddd2..a165a0b8c64 100644 --- a/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java +++ b/container-messagebus/src/main/java/com/yahoo/messagebus/jdisc/MbusServer.java @@ -10,13 +10,19 @@ import com.yahoo.jdisc.handler.ContentChannel; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.service.CurrentContainer; import com.yahoo.jdisc.service.ServerProvider; + +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; -import com.yahoo.messagebus.*; + +import com.yahoo.messagebus.EmptyReply; import com.yahoo.messagebus.Error; +import com.yahoo.messagebus.ErrorCode; +import com.yahoo.messagebus.Message; +import com.yahoo.messagebus.MessageHandler; +import com.yahoo.messagebus.Reply; import com.yahoo.messagebus.shared.ServerSession; import java.net.URI; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; /** @@ -24,8 +30,9 @@ import java.util.logging.Logger; */ public final class MbusServer extends AbstractResource implements ServerProvider, MessageHandler { + private enum State {INITIALIZING, RUNNING, STOPPED} private final static Logger log = Logger.getLogger(MbusServer.class.getName()); - private final AtomicBoolean running = new AtomicBoolean(false); + private final AtomicReference<State> runState = new AtomicReference<>(State.INITIALIZING); private final CurrentContainer container; private final ServerSession session; private final URI uri; @@ -44,26 +51,33 @@ public final class MbusServer extends AbstractResource implements ServerProvider public void start() { log.log(Level.FINE, "Starting message bus server."); session.connect(); - running.set(true); + runState.set(State.RUNNING); } @Override public void close() { log.log(Level.FINE, "Closing message bus server."); - running.set(false); + runState.set(State.STOPPED); } @Override protected void destroy() { log.log(Level.FINE, "Destroying message bus server."); - running.set(false); + runState.set(State.STOPPED); sessionReference.close(); } @Override public void handleMessage(Message msg) { - if (!running.get()) { - dispatchErrorReply(msg, ErrorCode.SESSION_BUSY, "Session temporarily closed."); + State state = runState.get(); + if (state == State.INITIALIZING) { + dispatchErrorReply(msg, ErrorCode.SESSION_BUSY, "MBusServer not started."); + return; + } + if (state == State.STOPPED) { + // We might need to detect requests originating from the same JVM, as they nede to fail fast + // As they are holding references to the container preventing proper shutdown. + dispatchErrorReply(msg, ErrorCode.SESSION_BUSY, "MBusServer has been closed."); return; } if (msg.getTrace().shouldTrace(6)) { diff --git a/container-messagebus/src/main/java/com/yahoo/messagebus/shared/ClientSession.java b/container-messagebus/src/main/java/com/yahoo/messagebus/shared/ClientSession.java index 0964a254cf2..22322a49a78 100644 --- a/container-messagebus/src/main/java/com/yahoo/messagebus/shared/ClientSession.java +++ b/container-messagebus/src/main/java/com/yahoo/messagebus/shared/ClientSession.java @@ -10,5 +10,5 @@ import com.yahoo.messagebus.Result; */ public interface ClientSession extends SharedResource { - public Result sendMessage(Message msg); + Result sendMessage(Message msg); } diff --git a/container-messagebus/src/test/java/com/yahoo/messagebus/jdisc/MbusServerConformanceTest.java b/container-messagebus/src/test/java/com/yahoo/messagebus/jdisc/MbusServerConformanceTest.java index bf89f3869ed..0c387456cf3 100644 --- a/container-messagebus/src/test/java/com/yahoo/messagebus/jdisc/MbusServerConformanceTest.java +++ b/container-messagebus/src/test/java/com/yahoo/messagebus/jdisc/MbusServerConformanceTest.java @@ -31,6 +31,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import static com.yahoo.messagebus.ErrorCode.APP_FATAL_ERROR; +import static com.yahoo.messagebus.ErrorCode.SEND_QUEUE_CLOSED; import static com.yahoo.messagebus.ErrorCode.SESSION_BUSY; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java index f02ba85c9bf..42a5e2b42be 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/athenz/ZmsClientMock.java @@ -17,6 +17,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import java.time.Instant; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -80,6 +81,25 @@ public class ZmsClientMock implements ZmsClient { } @Override + public void createTenantResourceGroup(AthenzDomain tenantDomain, AthenzIdentity provider, String resourceGroup, + Set<RoleAction> roleActions) { + log("createTenantResourceGroup(tenantDomain='%s', resourceGroup='%s')", tenantDomain, resourceGroup); + AthenzDbMock.Domain domain = getDomainOrThrow(tenantDomain, true); + ApplicationId applicationId = new ApplicationId(resourceGroup); + if (!domain.applications.containsKey(applicationId)) { + domain.applications.put(applicationId, new AthenzDbMock.Application()); + } + } + + @Override + public Set<RoleAction> getTenantResourceGroups(AthenzDomain tenantDomain, AthenzIdentity provider, String resourceGroup) { + Set<RoleAction> result = new HashSet<>(); + getDomainOrThrow(tenantDomain, true).applications.get(resourceGroup).acl + .forEach((role, roleMembers) -> result.add(new RoleAction(role.roleName, role.roleName))); + return result; + } + + @Override public void addRoleMember(AthenzRole role, AthenzIdentity member, Optional<String> reason) { if ( ! role.roleName().equals("tenancy.vespa.hosting.admin")) throw new IllegalArgumentException("Mock only supports adding tenant admins, not " + role.roleName()); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/NoopRoleService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/NoopRoleService.java index 970a70c6885..341b521212e 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/NoopRoleService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/NoopRoleService.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.aws; import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; import java.util.List; import java.util.Optional; @@ -12,7 +13,7 @@ import java.util.Optional; public class NoopRoleService implements RoleService { @Override - public Optional<TenantRoles> createTenantRole(TenantName tenant) { + public Optional<TenantRoles> createTenantRole(Tenant tenant) { return Optional.empty(); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/RoleService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/RoleService.java index d27fa0a5bd8..61007b9ff46 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/RoleService.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/aws/RoleService.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.aws; import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; import java.util.List; import java.util.Optional; @@ -11,7 +12,7 @@ import java.util.Optional; */ public interface RoleService { - Optional<TenantRoles> createTenantRole(TenantName tenant); + Optional<TenantRoles> createTenantRole(Tenant tenant); /** Retrieve the names of the tenant roles (host and container). Does not guarantee these roles exist */ TenantRoles getTenantRole(TenantName tenant); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/JobType.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/JobType.java index 9bcb80f24ee..bbcfb4e35a5 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/JobType.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/JobType.java @@ -139,6 +139,12 @@ public enum JobType { testCdUsCentral2 ("test-cd-us-central-2", Map.of(cd , ZoneId.from("prod" , "cd-us-central-2")), true), + productionCdUsEast1 ("production-cd-us-east-1", + Map.of(cd , ZoneId.from("prod" , "cd-us-east-1"))), + + testCdUsEast1 ("test-cd-us-east-1", + Map.of(cd , ZoneId.from("prod" , "cd-us-east-1")), true), + productionCdUsWest1 ("production-cd-us-west-1", Map.of(cd , ZoneId.from("prod" , "cd-us-west-1"))), diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java index 7539ef3c63a..f650f71c0ec 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java @@ -8,6 +8,7 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.RoutingMethod; import com.yahoo.config.provision.zone.UpgradePolicy; import com.yahoo.config.provision.zone.ZoneApi; @@ -89,4 +90,7 @@ public interface ZoneRegistry { /** Returns a URL to the controller's api endpoint */ URI apiUrl(); + /** IAM tenant developer role ARN */ + Optional<String> tenantDeveloperRoleArn(TenantName tenant); + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java index 7fa46031c98..7fa46031c98 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java index 1060b118beb..1060b118beb 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java index cf6d73cb8f8..cf6d73cb8f8 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/DeletedTenant.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java index 15f2f97e7d1..15f2f97e7d1 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java index 80982d70107..80982d70107 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfo.java index a20477d7aab..81c08e1083b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfo.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfo.java @@ -1,10 +1,6 @@ // Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.tenant; -import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore; - -import java.util.ArrayList; -import java.util.List; import java.util.Objects; /** diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoAddress.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoAddress.java index a12f351abd6..a12f351abd6 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoAddress.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoAddress.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoBillingContact.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoBillingContact.java index a00dd626f0a..a00dd626f0a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoBillingContact.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/TenantInfoBillingContact.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/package-info.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/package-info.java index 9218bfcd850..9218bfcd850 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/package-info.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/tenant/package-info.java diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java index ca173437dd1..a7555307a59 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java @@ -33,8 +33,6 @@ public class CuratorArchiveBucketDb { * Policy size limit is 20kb, about 550 bytes for non-tenant related policies. Each tenant * needs about 500 + len(role_arn) bytes, we limit role_arn to 100 characters, so we can * fit about (20k - 550) / 600 ~ 32 tenants per bucket. - * - * This limit is only enforced for public systems as non-public systems does not use tenant specific policies. */ private final static int TENANTS_PER_BUCKET = 30; @@ -86,7 +84,7 @@ public class CuratorArchiveBucketDb { .orElseGet(() -> { // If not, find an existing bucket with space Optional<ArchiveBucket> unfilledBucket = zoneBuckets.stream() - .filter(bucket -> !system.isPublic() || bucket.tenants().size() < TENANTS_PER_BUCKET) + .filter(bucket -> bucket.tenants().size() < TENANTS_PER_BUCKET) .findAny(); // And place the tenant in that bucket. diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java index 652f8630cb6..88d440b52b9 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java @@ -518,6 +518,8 @@ public class JobController { type, new Versions(platform.orElse(applicationPackage.deploymentSpec().majorVersion() .flatMap(controller.applications()::lastCompatibleVersion) + .or(() -> lastRun.map(run -> run.versions().targetPlatform()) + .filter(controller.readVersionStatus()::isActive)) .orElseGet(controller::readSystemVersion)), version, lastRun.map(run -> run.versions().targetPlatform()), diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java index b096a853541..6c4f5ffff9d 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java @@ -2,16 +2,25 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.google.common.collect.Maps; +import com.yahoo.config.provision.TenantName; +import com.yahoo.config.provision.zone.ZoneApi; +import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.jdisc.Metric; +import com.yahoo.vespa.flags.BooleanFlag; +import com.yahoo.vespa.flags.FetchVector; +import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveService; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import com.yahoo.vespa.hosted.controller.archive.CuratorArchiveBucketDb; +import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant; import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; import com.yahoo.vespa.hosted.controller.tenant.Tenant; import java.time.Duration; +import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -27,6 +36,8 @@ public class ArchiveAccessMaintainer extends ControllerMaintainer { private final ArchiveService archiveService; private final ZoneRegistry zoneRegistry; private final Metric metric; + private final BooleanFlag archiveEnabled; + private final BooleanFlag developerRoleEnabled; public ArchiveAccessMaintainer(Controller controller, Metric metric, Duration interval) { super(controller, interval); @@ -34,6 +45,8 @@ public class ArchiveAccessMaintainer extends ControllerMaintainer { this.archiveService = controller.serviceRegistry().archiveService(); this.zoneRegistry = controller().zoneRegistry(); this.metric = metric; + this.archiveEnabled = Flags.ENABLE_ONPREM_TENANT_S3_ARCHIVE.bindTo(controller().flagSource()); + this.developerRoleEnabled = Flags.ENABLE_TENANT_DEVELOPER_ROLE.bindTo(controller().flagSource()); } @Override @@ -43,22 +56,45 @@ public class ArchiveAccessMaintainer extends ControllerMaintainer { metric.set(bucketCountMetricName, archiveBucketDb.buckets(zoneId).size(), metric.createContext(Map.of("zone", zoneId.value())))); - var tenantArchiveAccessRoles = controller().tenants().asList().stream() - .filter(t -> t instanceof CloudTenant) - .map(t -> (CloudTenant) t) - .filter(t -> t.archiveAccessRole().isPresent()) - .collect(Collectors.toUnmodifiableMap( - Tenant::name, cloudTenant -> cloudTenant.archiveAccessRole().orElseThrow())); - zoneRegistry.zones().controllerUpgraded().ids().forEach(zoneId -> - archiveBucketDb.buckets(zoneId).forEach(archiveBucket -> - archiveService.updateBucketAndKeyPolicy(zoneId, archiveBucket, - Maps.filterEntries(tenantArchiveAccessRoles, - entry -> archiveBucket.tenants().contains(entry.getKey()))) - ) + zoneRegistry.zones().controllerUpgraded().zones().forEach(z -> { + ZoneId zoneId = z.getId(); + var tenantArchiveAccessRoles = tenantArchiveAccessRoles(z); + archiveBucketDb.buckets(zoneId).forEach(archiveBucket -> + archiveService.updateBucketAndKeyPolicy(zoneId, archiveBucket, + Maps.filterEntries(tenantArchiveAccessRoles, + entry -> archiveBucket.tenants().contains(entry.getKey()))) + ); + } ); return 1.0; } + private Map<TenantName, String> tenantArchiveAccessRoles(ZoneApi zone) { + List<Tenant> tenants = controller().tenants().asList(); + if (zoneRegistry.system().isPublic()) { + return tenants.stream() + .filter(t -> t instanceof CloudTenant) + .map(t -> (CloudTenant) t) + .filter(t -> t.archiveAccessRole().isPresent()) + .collect(Collectors.toUnmodifiableMap( + Tenant::name, cloudTenant -> cloudTenant.archiveAccessRole().orElseThrow())); + } else { + return tenants.stream() + .filter(t -> t instanceof AthenzTenant + && enabled(archiveEnabled, t, zone) && enabled(developerRoleEnabled, t, zone)) + .map(Tenant::name) + .collect(Collectors.toUnmodifiableMap( + Function.identity(), t -> zoneRegistry.tenantDeveloperRoleArn(t).orElseThrow())); + + } + } + + private boolean enabled(BooleanFlag flag, Tenant tenant, ZoneApi zone) { + return flag.with(FetchVector.Dimension.TENANT_ID, tenant.name().value()) + .with(FetchVector.Dimension.ZONE_ID, zone.getId().value()) + .value(); + } + } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java index 97e9a233f9f..0bf47f85420 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java @@ -46,7 +46,6 @@ public class DeploymentUpgrader extends ControllerMaintainer { Run last = controller().jobController().last(job).get(); Versions target = new Versions(systemVersion, last.versions().targetApplication(), Optional.of(last.versions().targetPlatform()), Optional.of(last.versions().targetApplication())); if ( ! deployment.version().isBefore(target.targetPlatform())) continue; - if ( controller().clock().instant().isBefore(last.start().plus(Duration.ofDays(1)))) continue; if ( ! isLikelyNightFor(job)) continue; log.log(Level.FINE, "Upgrading deployment of " + instance.id() + " in " + deployment.zone()); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java index ce6f9c802d6..d2b43dc63d9 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java @@ -27,9 +27,7 @@ public class TenantRoleMaintainer extends ControllerMaintainer { var tenants = controller().tenants().asList(); // Create separate athenz service for all tenants - tenants.stream() - .map(Tenant::name) - .forEach(roleService::createTenantRole); + tenants.forEach(roleService::createTenantRole); // Until we have moved to separate athenz service per tenant, make sure we update the shared policy // to allow ssh logins for hosts in prod/perf with a separate tenant iam role. diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiHandler.java index 6619b2ff5c6..61bf8feae35 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiHandler.java @@ -14,8 +14,6 @@ import com.yahoo.vespa.flags.FetchVector; import com.yahoo.vespa.flags.FlagSource; import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController; -import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; import com.yahoo.vespa.hosted.controller.api.integration.horizon.HorizonClient; import com.yahoo.vespa.hosted.controller.api.integration.horizon.HorizonResponse; import com.yahoo.vespa.hosted.controller.api.role.Role; @@ -28,7 +26,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.EnumSet; -import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.logging.Level; @@ -41,7 +38,6 @@ import java.util.stream.Collectors; */ public class HorizonApiHandler extends LoggingRequestHandler { - private final BillingController billingController; private final SystemName systemName; private final HorizonClient client; private final BooleanFlag enabledHorizonDashboard; @@ -52,7 +48,6 @@ public class HorizonApiHandler extends LoggingRequestHandler { @Inject public HorizonApiHandler(LoggingRequestHandler.Context parentCtx, Controller controller, FlagSource flagSource) { super(parentCtx); - this.billingController = controller.serviceRegistry().billingController(); this.systemName = controller.system(); this.client = controller.serviceRegistry().horizonClient(); this.enabledHorizonDashboard = Flags.ENABLED_HORIZON_DASHBOARD.bindTo(flagSource); @@ -123,13 +118,11 @@ public class HorizonApiHandler extends LoggingRequestHandler { } private Set<TenantName> getAuthorizedTenants(Set<Role> roles) { - var horizonEnabled = roles.stream() + return roles.stream() .filter(TenantRole.class::isInstance) .map(role -> ((TenantRole) role).tenant()) .filter(tenant -> enabledHorizonDashboard.with(FetchVector.Dimension.TENANT_ID, tenant.value()).value()) - .collect(Collectors.toList()); - - return new HashSet<>(billingController.tenantsWithPlan(horizonEnabled, PlanId.from("pay-as-you-go"))); + .collect(Collectors.toSet()); } private static class JsonInputStreamResponse extends HttpResponse { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java index 157f57b3bea..7e88f127026 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java @@ -20,10 +20,7 @@ import com.yahoo.slime.SlimeStream; import com.yahoo.slime.SlimeUtils; import com.yahoo.text.Text; import com.yahoo.vespa.configserver.flags.FlagsDb; -import com.yahoo.vespa.flags.BooleanFlag; -import com.yahoo.vespa.flags.FetchVector; import com.yahoo.vespa.flags.FlagSource; -import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.flags.IntFlag; import com.yahoo.vespa.flags.PermanentFlags; import com.yahoo.vespa.hosted.controller.Controller; @@ -71,9 +68,7 @@ public class UserApiHandler extends LoggingRequestHandler { private final UserManagement users; private final Controller controller; private final FlagsDb flagsDb; - private final BooleanFlag enable_public_signup_flow; private final IntFlag maxTrialTenants; - private final BooleanFlag enabledHorizonDashboard; @Inject public UserApiHandler(Context parentCtx, UserManagement users, Controller controller, FlagSource flagSource, FlagsDb flagsDb) { @@ -81,9 +76,7 @@ public class UserApiHandler extends LoggingRequestHandler { this.users = users; this.controller = controller; this.flagsDb = flagsDb; - this.enable_public_signup_flow = PermanentFlags.ENABLE_PUBLIC_SIGNUP_FLOW.bindTo(flagSource); this.maxTrialTenants = PermanentFlags.MAX_TRIAL_TENANTS.bindTo(flagSource); - this.enabledHorizonDashboard = Flags.ENABLED_HORIZON_DASHBOARD.bindTo(flagSource); } @Override @@ -173,9 +166,6 @@ public class UserApiHandler extends LoggingRequestHandler { root.setBool("isPublic", controller.system().isPublic()); root.setBool("isCd", controller.system().isCd()); - // TODO (freva): Remove after users have migrated to use 'flags' - root.setBool(enable_public_signup_flow.id().toString(), - enable_public_signup_flow.with(FetchVector.Dimension.CONSOLE_USER_EMAIL, user.email()).value()); root.setBool("hasTrialCapacity", hasTrialCapacity()); toSlime(root.setObject("user"), user); @@ -190,10 +180,6 @@ public class UserApiHandler extends LoggingRequestHandler { Cursor tenantRolesObject = tenantObject.setArray("roles"); tenantRolesByTenantName.getOrDefault(tenant, List.of()) .forEach(role -> tenantRolesObject.addString(role.definition().name())); - if (controller.system().isPublic()) { - tenantObject.setBool(enabledHorizonDashboard.id().toString(), - enabledHorizonDashboard.with(FetchVector.Dimension.TENANT_ID, tenant.value()).value()); - } }); if (!operatorRoles.isEmpty()) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java index fe03b69a3fe..7ce17aff782 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java @@ -9,6 +9,7 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.RoutingMethod; import com.yahoo.config.provision.zone.UpgradePolicy; import com.yahoo.config.provision.zone.ZoneApi; @@ -216,6 +217,8 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry return URI.create("https://api.tld:4443/"); } + @Override public Optional<String> tenantDeveloperRoleArn(TenantName tenant) { return Optional.empty(); } + @Override public boolean hasZone(ZoneId zoneId) { return zones.stream().anyMatch(zone -> zone.getId().equals(zoneId)); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgraderTest.java index ec33c8a7048..1c07a321953 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgraderTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgraderTest.java @@ -55,19 +55,13 @@ public class DeploymentUpgraderTest { assertEquals(start, tester.jobs().last(devApp.instanceId(), devUsEast1).get().start()); assertEquals(start, tester.jobs().last(prodApp.instanceId(), productionUsWest1).get().start()); - // 14 hours pass, but not upgraded before a day has passed since last deployment - tester.clock().advance(Duration.ofHours(14)); + // 11 hours pass, but not upgraded since it's not likely in the middle of the night + tester.clock().advance(Duration.ofHours(11)); upgrader.maintain(); assertEquals(start, tester.jobs().last(devApp.instanceId(), devUsEast1).get().start()); assertEquals(start, tester.jobs().last(prodApp.instanceId(), productionUsWest1).get().start()); - // 35 hours pass, but not upgraded since it's not likely in the middle of the night - tester.clock().advance(Duration.ofHours(21)); - upgrader.maintain(); - assertEquals(start, tester.jobs().last(devApp.instanceId(), devUsEast1).get().start()); - assertEquals(start, tester.jobs().last(prodApp.instanceId(), productionUsWest1).get().start()); - - // 38 hours pass, and the dev deployment, only, is upgraded + // 14 hours pass, and the dev deployment, only, is upgraded tester.clock().advance(Duration.ofHours(3)); upgrader.maintain(); assertEquals(tester.clock().instant().truncatedTo(MILLIS), tester.jobs().last(devApp.instanceId(), devUsEast1).get().start()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index cd73721ce4f..03a88c7a8c4 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -1,4 +1,4 @@ -// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.restapi.application; import ai.vespa.hosted.api.MultiPartStreamer; @@ -250,7 +250,7 @@ public class ApplicationApiTest extends ControllerContainerTest { var app1 = deploymentTester.newDeploymentContext(id); // POST (deploy) an application to start a manual deployment in prod is not allowed - MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1, true); + MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploy/production-us-east-3/", POST) .data(entity) .userIdentity(USER_ID), @@ -289,7 +289,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // POST an application package is not generally allowed under user instance tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/otheruser/deploy/dev-us-east-1", POST) .userIdentity(OTHER_USER_ID) - .data(createApplicationDeployData(applicationPackageInstance1, false)), + .data(createApplicationDeployData(applicationPackageInstance1)), accessDenied, 403); @@ -303,7 +303,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // POST an application package is not allowed under user instance for tenant admins tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/myuser/deploy/dev-us-east-1", POST) .userIdentity(USER_ID) - .data(createApplicationDeployData(applicationPackageInstance1, false)), + .data(createApplicationDeployData(applicationPackageInstance1)), new File("deployment-job-accepted-2.json")); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/myuser/job/dev-us-east-1/diff/1", GET).userIdentity(HOSTED_VESPA_OPERATOR), @@ -1026,38 +1026,24 @@ public class ApplicationApiTest extends ControllerContainerTest { } @Test - public void testDeployDirectly() { + public void testDeployWithApplicationPackage() { // Setup - createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR)); - // Create tenant - tester.assertResponse(request("/application/v4/tenant/tenant1", POST).userIdentity(USER_ID) - .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") - .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), - new File("tenant-without-applications.json")); - - // Create application - tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1", POST) - .userIdentity(USER_ID) - .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), - new File("instance-reference.json")); - - // Add build service to operator role - addUserToHostedOperatorRole(HostedAthenzIdentities.from(SCREWDRIVER_ID)); - // POST (deploy) a system application with an application package - MultiPartStreamer noAppEntity = createApplicationDeployData(Optional.empty(), true); + MultiPartStreamer noAppEntity = createApplicationDeployData(Optional.empty()); tester.assertResponse(request("/application/v4/tenant/hosted-vespa/application/routing/environment/prod/region/us-central-1/instance/default/deploy", POST) .data(noAppEntity) .userIdentity(HOSTED_VESPA_OPERATOR), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Deployment of system applications during a system upgrade is not allowed\"}", 400); - deploymentTester.controllerTester().upgradeSystem(deploymentTester.controller().readVersionStatus().controllerVersion().get().versionNumber()); + deploymentTester.controllerTester() + .upgradeSystem(deploymentTester.controller().readVersionStatus().controllerVersion().get() + .versionNumber()); tester.assertResponse(request("/application/v4/tenant/hosted-vespa/application/routing/environment/prod/region/us-central-1/instance/default/deploy", POST) - .data(noAppEntity) - .userIdentity(HOSTED_VESPA_OPERATOR), - new File("deploy-result.json")); + .data(noAppEntity) + .userIdentity(HOSTED_VESPA_OPERATOR), + new File("deploy-result.json")); } @Test @@ -1113,7 +1099,7 @@ public class ApplicationApiTest extends ControllerContainerTest { } @Test - public void testErrorResponses() throws Exception { + public void testErrorResponses() { createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); // PUT (update) non-existing tenant returns 403 as tenant access cannot be determined when the tenant does not exist @@ -1223,7 +1209,7 @@ public class ApplicationApiTest extends ControllerContainerTest { 400); // POST (deploy) an application to legacy deploy path - MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1, true); + MultiPartStreamer entity = createApplicationDeployData(applicationPackageInstance1); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/dev/region/us-east-1/instance/instance1/deploy", POST) .data(entity) .userIdentity(USER_ID), @@ -1330,7 +1316,7 @@ public class ApplicationApiTest extends ControllerContainerTest { 200); // Deploy to an authorized zone by a user tenant is disallowed - MultiPartStreamer entity = createApplicationDeployData(applicationPackageDefault, true); + MultiPartStreamer entity = createApplicationDeployData(applicationPackageDefault); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-west-1/instance/default/deploy", POST) .data(entity) .userIdentity(USER_ID), @@ -1437,7 +1423,7 @@ public class ApplicationApiTest extends ControllerContainerTest { .build(); createTenantAndApplication(); - MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true); + MultiPartStreamer entity = createApplicationDeployData(applicationPackage); // POST (deploy) an application to dev through a deployment job, with user instance and a proper tenant tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/new-user/deploy/dev-us-east-1", POST) .data(entity) @@ -1484,7 +1470,7 @@ public class ApplicationApiTest extends ControllerContainerTest { .build(); // deploy the application to a dev zone. Should fail since the developer is not authorized to launch the service - MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true); + MultiPartStreamer entity = createApplicationDeployData(applicationPackage); tester.assertResponse(request("/application/v4/tenant/sandbox/application/myapp/instance/default/deploy/dev-us-east-1", POST) .data(entity) .userIdentity(developer), @@ -1646,7 +1632,7 @@ public class ApplicationApiTest extends ControllerContainerTest { } @Test - public void testServiceView() throws Exception { + public void testServiceView() { createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); String serviceApi="/application/v4/tenant/tenant1/application/application1/environment/prod/region/us-central-1/instance/instance1/service"; // Not allowed to request apis not listed in feature flag allowed-service-view-apis. e.g /document/v1 @@ -1713,18 +1699,18 @@ public class ApplicationApiTest extends ControllerContainerTest { .build(); } - private MultiPartStreamer createApplicationDeployData(ApplicationPackage applicationPackage, boolean deployDirectly) { - return createApplicationDeployData(Optional.of(applicationPackage), deployDirectly); + private MultiPartStreamer createApplicationDeployData(ApplicationPackage applicationPackage) { + return createApplicationDeployData(Optional.of(applicationPackage)); } - private MultiPartStreamer createApplicationDeployData(Optional<ApplicationPackage> applicationPackage, boolean deployDirectly) { - return createApplicationDeployData(applicationPackage, Optional.empty(), deployDirectly); + private MultiPartStreamer createApplicationDeployData(Optional<ApplicationPackage> applicationPackage) { + return createApplicationDeployData(applicationPackage, Optional.empty()); } private MultiPartStreamer createApplicationDeployData(Optional<ApplicationPackage> applicationPackage, - Optional<ApplicationVersion> applicationVersion, boolean deployDirectly) { + Optional<ApplicationVersion> applicationVersion) { MultiPartStreamer streamer = new MultiPartStreamer(); - streamer.addJson("deployOptions", deployOptions(deployDirectly, applicationVersion)); + streamer.addJson("deployOptions", deployOptions(applicationVersion)); applicationPackage.ifPresent(ap -> streamer.addBytes("applicationZip", ap.zippedContent())); return streamer; } @@ -1736,10 +1722,9 @@ public class ApplicationApiTest extends ControllerContainerTest { .addBytes(EnvironmentResource.APPLICATION_TEST_ZIP, "content".getBytes()); } - private String deployOptions(boolean deployDirectly, Optional<ApplicationVersion> applicationVersion) { + private String deployOptions(Optional<ApplicationVersion> applicationVersion) { return "{\"vespaVersion\":null," + - "\"ignoreValidationErrors\":false," + - "\"deployDirectly\":" + deployDirectly + + "\"ignoreValidationErrors\":false" + applicationVersion.map(version -> "," + "\"buildNumber\":" + version.buildNumber().getAsLong() + "," + diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiTest.java index 8e51f8210c7..b2b5b2286f7 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiTest.java @@ -4,7 +4,6 @@ import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.flags.InMemoryFlagSource; -import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; import com.yahoo.vespa.hosted.controller.api.role.Role; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest; @@ -33,8 +32,6 @@ public class HorizonApiTest extends ControllerContainerCloudTest { ((InMemoryFlagSource) tester.controller().flagSource()) .withBooleanFlag(Flags.ENABLED_HORIZON_DASHBOARD.id(), true); - tester.controller().serviceRegistry().billingController().setPlan(tenantName, PlanId.from("pay-as-you-go"), true); - tester.assertResponse(request("/horizon/v1/config/dashboard/topFolders") .roles(Set.of(Role.reader(tenantName))), "", 200); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json index 0a416600b2c..006c3b98a4d 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-athenz.json @@ -1,7 +1,6 @@ { "isPublic": false, "isCd": false, - "enable-public-signup-flow": (ignore), "hasTrialCapacity": (ignore), "user": { "name": "Joe Developer", diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json index 4e179ad83c5..4ae55e97baa 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-with-applications-cloud.json @@ -1,7 +1,6 @@ { "isPublic": true, "isCd": false, - "enable-public-signup-flow": (ignore), "hasTrialCapacity": true, "user": { "name": "Joe Developer", @@ -14,20 +13,17 @@ "roles": [ "developer", "reader" - ], - "enabled-horizon-dashboard":false + ] }, "tenant1": { "roles": [ "administrator" - ], - "enabled-horizon-dashboard":false + ] }, "tenant2": { "roles": [ "developer" - ], - "enabled-horizon-dashboard":false + ] } }, "flags": [{"id":"enable-public-signup-flow","rules":[{"value":false}]}] diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-applications.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-applications.json index 7eb445140e7..9f9578e6ed8 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-applications.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-applications.json @@ -1,7 +1,6 @@ { "isPublic": (ignore), "isCd": (ignore), - "enable-public-signup-flow": (ignore), "hasTrialCapacity": (ignore), "user": { "name": "Joe Developer", diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-trial-capacity-cloud.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-trial-capacity-cloud.json index 3c1edab8cfc..2b98a75068a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-trial-capacity-cloud.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/user-without-trial-capacity-cloud.json @@ -1,7 +1,6 @@ { "isPublic": true, "isCd": false, - "enable-public-signup-flow": true, "hasTrialCapacity": false, "user": { "name": "Joe Developer", diff --git a/dist/vespa.spec b/dist/vespa.spec index e976d710fb5..e45c45d41f9 100644 --- a/dist/vespa.spec +++ b/dist/vespa.spec @@ -79,6 +79,7 @@ BuildRequires: cmake3 BuildRequires: llvm7.0-devel BuildRequires: vespa-boost-devel >= 1.76.0-1 BuildRequires: vespa-gtest >= 1.8.1-1 +%define _use_vespa_gtest 1 BuildRequires: vespa-icu-devel >= 65.1.0-1 BuildRequires: vespa-lz4-devel >= 1.9.2-2 BuildRequires: vespa-onnxruntime-devel = 1.7.1 @@ -108,6 +109,7 @@ BuildRequires: vespa-boost-devel >= 1.76.0-1 BuildRequires: vespa-openssl-devel >= 1.1.1l-1 %define _use_vespa_openssl 1 BuildRequires: vespa-gtest >= 1.8.1-1 +%define _use_vespa_gtest 1 BuildRequires: vespa-lz4-devel >= 1.9.2-2 BuildRequires: vespa-onnxruntime-devel = 1.7.1 BuildRequires: vespa-protobuf-devel = 3.17.3 @@ -233,6 +235,7 @@ Requires: llvm7.0 Requires: vespa-telegraf >= 1.1.1-1 Requires: vespa-valgrind >= 3.17.0-1 %endif +Requires: vespa-gtest >= 1.8.1-1 %define _vespa_llvm_version 7 %define _extra_link_directory /usr/lib64/llvm7.0/lib;%{_vespa_deps_prefix}/lib64 %define _extra_include_directory /usr/include/llvm7.0;%{_vespa_deps_prefix}/include @@ -247,10 +250,12 @@ Requires: vespa-valgrind >= 3.17.0-1 %else %define _vespa_llvm_version 10 %endif +Requires: vespa-gtest >= 1.8.1-1 %define _extra_link_directory %{_vespa_deps_prefix}/lib64 %define _extra_include_directory %{_vespa_deps_prefix}/include %endif %if 0%{?fedora} +Requires: gtest %if 0%{?fc32} %define _vespa_llvm_version 10 %endif @@ -285,7 +290,7 @@ Requires: %{name}-tools = %{version}-%{release} # Ugly workaround because vespamalloc/src/vespamalloc/malloc/mmap.cpp uses the private # _dl_sym function. # Exclude automated requires for libraries in /opt/vespa-deps/lib64. -%global __requires_exclude ^lib(c\\.so\\.6\\(GLIBC_PRIVATE\\)|pthread\\.so\\.0\\(GLIBC_PRIVATE\\)|(icui18n|icuuc|lz4|protobuf|zstd|onnxruntime%{?_use_vespa_openssl:|crypto|ssl}%{?_use_vespa_openblas:|openblas}%{?_use_vespa_re2:|re2}%{?_use_vespa_xxhash:|xxhash})\\.so\\.[0-9.]*\\([A-Za-z._0-9]*\\))\\(64bit\\)$ +%global __requires_exclude ^lib(c\\.so\\.6\\(GLIBC_PRIVATE\\)|pthread\\.so\\.0\\(GLIBC_PRIVATE\\)|(icui18n|icuuc|lz4|protobuf|zstd|onnxruntime%{?_use_vespa_openssl:|crypto|ssl}%{?_use_vespa_openblas:|openblas}%{?_use_vespa_re2:|re2}%{?_use_vespa_xxhash:|xxhash}%{?_use_vespa_gtest:|(gtest|gmock)(_main)?})\\.so\\.[0-9.]*\\([A-Za-z._0-9]*\\))\\(64bit\\)$ %description diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/GetDocumentMessage.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/GetDocumentMessage.java index f65550bbf0f..e0515a84b5d 100755 --- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/GetDocumentMessage.java +++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/GetDocumentMessage.java @@ -16,13 +16,6 @@ public class GetDocumentMessage extends DocumentMessage { private String fieldSet = DEFAULT_FIELD_SET; /** - * Constructs a new message for deserialization. - */ - GetDocumentMessage() { - // empty - } - - /** * Constructs a new document get message. * * @param documentId The identifier of the document to get. diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java index b2bf26d3b05..58bc50088d7 100755 --- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java +++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java @@ -19,13 +19,6 @@ public class PutDocumentMessage extends TestAndSetMessage { private LazyDecoder decoder = null; /** - * Constructs a new message for deserialization. - */ - PutDocumentMessage() { - // empty - } - - /** * Constructs a new message from a byte buffer. * * @param decoder The decoder to use for deserialization. diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index d4157b659ae..28b031e5015 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -79,13 +79,6 @@ public class Flags { "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); - public static final UnboundBooleanFlag ENFORCE_RANK_PROFILE_INHERITANCE = defineFeatureFlag( - "enforce-rank-profile-inheritance", true, - List.of("baldersheim"), "2021-09-07", "2021-10-01", - "Should we enforce verification of rank-profile inheritance.", - "Takes effect at redeployment", - ZONE_ID, APPLICATION_ID); - public static final UnboundBooleanFlag SKIP_MBUS_REQUEST_THREAD = defineFeatureFlag( "skip-mbus-request-thread", false, List.of("baldersheim"), "2020-12-02", "2022-01-01", @@ -172,6 +165,20 @@ public class Flags { "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); + public static final UnboundBooleanFlag CONTAINER_DUMP_HEAP_ON_SHUTDOWN_TIMEOUT = defineFeatureFlag( + "container-dump-heap-on-shutdown-timeout", false, + List.of("baldersheim"), "2021-09-25", "2021-11-01", + "Will trigger a heap dump during if container shutdown times out", + "Takes effect at redeployment", + ZONE_ID, APPLICATION_ID); + + public static final UnboundDoubleFlag CONTAINER_SHUTDOWN_TIMEOUT = defineDoubleFlag( + "container-shutdown-timeout", 50.0, + List.of("baldersheim"), "2021-09-25", "2021-11-01", + "Timeout for shutdown of a jdisc container", + "Takes effect at redeployment", + ZONE_ID, APPLICATION_ID); + public static final UnboundListFlag<String> ALLOWED_ATHENZ_PROXY_IDENTITIES = defineListFlag( "allowed-athenz-proxy-identities", List.of(), String.class, List.of("bjorncs", "tokle"), "2021-02-10", "2021-12-01", @@ -298,6 +305,15 @@ public class Flags { APPLICATION_ID ); + public static final UnboundBooleanFlag ENABLE_TENANT_DEVELOPER_ROLE = defineFeatureFlag( + "enable-tenant-developer-role", false, + List.of("bjorncs"), "2021-09-23", "2021-12-31", + "Enable tenant developer Athenz role in cd/main. Must be set on controller cluster only.", + "Takes effect immediately", + TENANT_ID + ); + + /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List<String> owners, String createdAt, String expiresAt, String description, diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java b/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java index 6a733bd8942..487c60e0dcb 100644 --- a/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java +++ b/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java @@ -340,7 +340,6 @@ public abstract class ControllerHttpClient { Slime slime = new Slime(); Cursor rootObject = slime.setObject(); deployment.version().ifPresent(version -> rootObject.setString("vespaVersion", version)); - rootObject.setBool("deployDirectly", true); return toJson(slime); } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/core/ContainerWatchdog.java b/jdisc_core/src/main/java/com/yahoo/jdisc/core/ContainerWatchdog.java index 30aa0028465..7e82955eede 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/core/ContainerWatchdog.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/core/ContainerWatchdog.java @@ -22,8 +22,9 @@ import java.util.logging.Logger; */ class ContainerWatchdog implements ContainerWatchdogMetrics, AutoCloseable { - static final Duration GRACE_PERIOD = Duration.ofMinutes(30); + static final Duration GRACE_PERIOD = Duration.ofMinutes(5); static final Duration UPDATE_PERIOD = Duration.ofMinutes(5); + static final Duration CHECK_PERIOD = Duration.ofSeconds(1); private static final Logger log = Logger.getLogger(ContainerWatchdog.class.getName()); @@ -35,6 +36,7 @@ class ContainerWatchdog implements ContainerWatchdogMetrics, AutoCloseable { private ActiveContainer currentContainer; private Instant currentContainerActivationTime; private int numStaleContainers; + private Instant lastLogTime; ContainerWatchdog() { this(new ScheduledThreadPoolExecutor( @@ -50,8 +52,8 @@ class ContainerWatchdog implements ContainerWatchdogMetrics, AutoCloseable { ContainerWatchdog(ScheduledExecutorService scheduler, Clock clock) { this.scheduler = scheduler; this.clock = clock; - scheduler.scheduleAtFixedRate( - this::monitorDeactivatedContainers, UPDATE_PERIOD.getSeconds(), UPDATE_PERIOD.getSeconds(), TimeUnit.SECONDS); + this.lastLogTime = clock.instant(); + scheduler.scheduleAtFixedRate(this::monitorDeactivatedContainers, CHECK_PERIOD.getSeconds(), CHECK_PERIOD.getSeconds(), TimeUnit.SECONDS); } @Override @@ -77,31 +79,43 @@ class ContainerWatchdog implements ContainerWatchdogMetrics, AutoCloseable { void onContainerActivation(ActiveContainer nextContainer) { synchronized (monitor) { if (currentContainer != null) { - deactivatedContainers.add( - new DeactivatedContainer(currentContainer, currentContainerActivationTime, clock.instant())); + deactivatedContainers.add(new DeactivatedContainer(currentContainer, currentContainerActivationTime, clock.instant())); } currentContainer = nextContainer; currentContainerActivationTime = clock.instant(); } } + private String removalMsg(DeactivatedContainer container) { + return String.format("Removing deactivated container: instance=%s, activated=%s, deactivated=%s", + container.instance, container.timeActivated, container.timeDeactivated); + } + + private String regularMsg(DeactivatedContainer container, int refCount) { + return String.format( + "Deactivated container still alive: instance=%s, activated=%s, deactivated=%s, ref-count=%d", + container.instance, container.timeActivated, container.timeDeactivated, refCount); + } + void monitorDeactivatedContainers() { synchronized (monitor) { int numStaleContainer = 0; Iterator<DeactivatedContainer> iterator = deactivatedContainers.iterator(); + boolean timeToLogAgain = clock.instant().isAfter(lastLogTime.plus(UPDATE_PERIOD)); while (iterator.hasNext()) { DeactivatedContainer container = iterator.next(); int refCount = container.instance.retainCount(); if (refCount == 0) { + log.fine(removalMsg(container)); iterator.remove(); - break; - } - if (isPastGracePeriod(container)) { - ++numStaleContainer; - log.warning( - String.format( - "Deactivated container still alive: instance=%s, activated=%s, deactivated=%s, ref-count=%d", - container.instance.toString(), container.timeActivated, container.timeDeactivated, refCount)); + } else { + if (isPastGracePeriod(container)) { + ++numStaleContainer; + if (timeToLogAgain) { + log.warning(regularMsg(container, refCount)); + lastLogTime = clock.instant(); + } + } } } this.numStaleContainers = numStaleContainer; diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/service/ClientProvider.java b/jdisc_core/src/main/java/com/yahoo/jdisc/service/ClientProvider.java index c178147a952..a47c6b06321 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/service/ClientProvider.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/service/ClientProvider.java @@ -2,7 +2,11 @@ package com.yahoo.jdisc.service; import com.yahoo.jdisc.Container; -import com.yahoo.jdisc.application.*; +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.application.ContainerActivator; +import com.yahoo.jdisc.application.ContainerBuilder; +import com.yahoo.jdisc.application.Application; +import com.yahoo.jdisc.application.UriPattern; import com.yahoo.jdisc.handler.RequestHandler; /** diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java b/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java index b58f3bc5138..3b5cbfd9cbc 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/service/ServerProvider.java @@ -37,7 +37,7 @@ public interface ServerProvider extends SharedResource { * {@link Container} does <em>not</em> call this method, instead it is a required step in the {@link Application} * initialization code.</p> */ - public void start(); + void start(); /** * <p>This is a synchronous method to close the listen port (or equivalent) of this ServerProvider and flush any @@ -48,5 +48,5 @@ public interface ServerProvider extends SharedResource { * <p>The {@link Container} does <em>not</em> call this method, instead it is a required step in the {@link * Application} shutdown code.</p> */ - public void close(); + void close(); } diff --git a/linguistics/src/main/resources/configdefinitions/sentence-piece.def b/linguistics/src/main/resources/configdefinitions/language.sentencepiece.sentence-piece.def index b91c0c45dc4..0b8e5103aa3 100644 --- a/linguistics/src/main/resources/configdefinitions/sentence-piece.def +++ b/linguistics/src/main/resources/configdefinitions/language.sentencepiece.sentence-piece.def @@ -15,4 +15,4 @@ scoring enum { highestScore, fewestSegments } default=fewestSegments # Use "unknown" for models to be used with any language. model[].language string # The path to the model relative to the application package root -model[].path path
\ No newline at end of file +model[].path path diff --git a/logd/src/logd/log_protocol_proto.h b/logd/src/logd/log_protocol_proto.h index 6f82b5d28b8..2fcb7d03919 100644 --- a/logd/src/logd/log_protocol_proto.h +++ b/logd/src/logd/log_protocol_proto.h @@ -7,7 +7,7 @@ #pragma GCC diagnostic ignored "-Wsuggest-override" #endif -#include "log_protocol.pb.h" +#include <logd/log_protocol.pb.h> #pragma GCC diagnostic pop diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java b/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java index e75b5d0934a..4bd9bbf7f7f 100644 --- a/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/NetworkMultiplexer.java @@ -7,10 +7,9 @@ import com.yahoo.text.Utf8Array; import java.util.Deque; import java.util.Map; -import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; /** @@ -33,15 +32,16 @@ public class NetworkMultiplexer implements NetworkOwner { private final Network net; private final Deque<NetworkOwner> owners = new ConcurrentLinkedDeque<>(); private final Map<String, Deque<NetworkOwner>> sessions = new ConcurrentHashMap<>(); - private final boolean shared; + private final AtomicBoolean disowned; private NetworkMultiplexer(Network net, boolean shared) { net.attach(this); this.net = net; - this.shared = shared; + this.disowned = new AtomicBoolean( ! shared); } - /** Returns a network multiplexer which will be shared between several {@link NetworkOwner}s. */ + /** Returns a network multiplexer which will be shared between several {@link NetworkOwner}s, + * and will shut down when all these have detached, and {@link #disown()} has been called, in any order. */ public static NetworkMultiplexer shared(Network net) { return new NetworkMultiplexer(net, true); } @@ -100,6 +100,7 @@ public class NetworkMultiplexer implements NetworkOwner { owner.deliverMessage(message, session); } + /** Attach the network owner to this, allowing this to forward messages to it. */ public void attach(NetworkOwner owner) { if (owners.contains(owner)) throw new IllegalArgumentException(owner + " is already attached to this"); @@ -107,23 +108,27 @@ public class NetworkMultiplexer implements NetworkOwner { owners.add(owner); } + /** Detach the network owner from this, no longer allowing messages to it, and shutting down this is ownerless. */ public void detach(NetworkOwner owner) { if ( ! owners.remove(owner)) throw new IllegalArgumentException(owner + " not attached to this"); - if ( ! shared && owners.isEmpty()) - net.shutdown(); + destroyIfOwnerless(); } - public void destroy() { - if ( ! shared) - throw new UnsupportedOperationException("Destroy called on a dedicated multiplexer; " + - "this automatically shuts down when detached from"); + /** Signal that external ownership of this is relinquished, allowing destruction on last owner detachment. */ + public void disown() { + if (disowned.getAndSet(true)) + throw new IllegalStateException("Destroy called on a dedicated multiplexer--" + + "this automatically shuts down when detached from--or " + + "called multiple times on a shared multiplexer"); - if ( ! owners.isEmpty()) - log.warning("NetworkMultiplexer destroyed before all owners detached: " + this); + destroyIfOwnerless(); + } - net.shutdown(); + private void destroyIfOwnerless() { + if (disowned.get() && owners.isEmpty()) + net.shutdown(); } public Network net() { @@ -136,7 +141,7 @@ public class NetworkMultiplexer implements NetworkOwner { "net=" + net + ", owners=" + owners + ", sessions=" + sessions + - ", shared=" + shared + + ", destructible=" + disowned + '}'; } diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/NamedRPCService.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/NamedRPCService.java new file mode 100644 index 00000000000..59cafed1836 --- /dev/null +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/NamedRPCService.java @@ -0,0 +1,48 @@ +package com.yahoo.messagebus.network.rpc; + +import com.yahoo.jrt.slobrok.api.IMirror; +import com.yahoo.jrt.slobrok.api.Mirror; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class NamedRPCService implements RPCService { + private final IMirror mirror; + private final String pattern; + private int addressIdx = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE); + private int addressGen = 0; + private List<Mirror.Entry> addressList = null; + + /** + * Create a new RPCService backed by the given network and using the given service pattern. + * + * @param mirror The naming server to send queries to. + * @param pattern The pattern to use when querying. + */ + public NamedRPCService(IMirror mirror, String pattern) { + this.mirror = mirror; + this.pattern = pattern; + } + + /** + * Resolve a concrete address from this service. This service may represent multiple remote sessions, so this will + * select one that is online. + * + * @return A concrete service address. + */ + public synchronized RPCServiceAddress resolve() { + if (addressGen != mirror.updates()) { + addressGen = mirror.updates(); + addressList = mirror.lookup(pattern); + } + if (addressList != null && !addressList.isEmpty()) { + ++addressIdx; + if (addressIdx >= addressList.size()) { + addressIdx = 0; + } + Mirror.Entry entry = addressList.get(addressIdx); + return new RPCServiceAddress(entry.getName(), entry.getSpec()); + } + return null; + } +} diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java index fb3c4cf9971..889df32ce1e 100644 --- a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCService.java @@ -2,10 +2,6 @@ package com.yahoo.messagebus.network.rpc; import com.yahoo.jrt.slobrok.api.IMirror; -import com.yahoo.jrt.slobrok.api.Mirror; - -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; /** * An RPCService represents a set of remote sessions matching a service pattern. The sessions are monitored using the @@ -13,23 +9,13 @@ import java.util.concurrent.ThreadLocalRandom; * * @author havardpe */ -public class RPCService { - - private final IMirror mirror; - private final String pattern; - private int addressIdx = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE); - private int addressGen = 0; - private List<Mirror.Entry> addressList = null; +public interface RPCService { - /** - * Create a new RPCService backed by the given network and using the given service pattern. - * - * @param mirror The naming server to send queries to. - * @param pattern The pattern to use when querying. - */ - public RPCService(IMirror mirror, String pattern) { - this.mirror = mirror; - this.pattern = pattern; + static RPCService create(IMirror mirror, String pattern) { + if (pattern.startsWith("tcp/")) { + return new TcpRPCService(pattern); + } + return new NamedRPCService(mirror, pattern); } /** @@ -38,38 +24,6 @@ public class RPCService { * * @return A concrete service address. */ - public RPCServiceAddress resolve() { - if (pattern.startsWith("tcp/")) { - int pos = pattern.lastIndexOf('/'); - if (pos > 0 && pos < pattern.length() - 1) { - RPCServiceAddress ret = new RPCServiceAddress(pattern, pattern.substring(0, pos)); - if (!ret.isMalformed()) { - return ret; - } - } - } else { - if (addressGen != mirror.updates()) { - addressGen = mirror.updates(); - addressList = mirror.lookup(pattern); - } - if (addressList != null && !addressList.isEmpty()) { - ++addressIdx; - if (addressIdx >= addressList.size()) { - addressIdx = 0; - } - Mirror.Entry entry = addressList.get(addressIdx); - return new RPCServiceAddress(entry.getName(), entry.getSpec()); - } - } - return null; - } + RPCServiceAddress resolve(); - /** - * Returns the pattern used when querying for the naming server for addresses. This is given at construtor time. - * - * @return The service pattern. - */ - String getPattern() { - return pattern; - } } diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java index 0a6a58d4e89..1b7bcf01731 100755 --- a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServiceAddress.java @@ -34,6 +34,13 @@ public class RPCServiceAddress implements ServiceAddress { public RPCServiceAddress(String serviceName, String connectionSpec) { this(serviceName, new Spec(connectionSpec)); } + public RPCServiceAddress(RPCServiceAddress blueprint) { + serviceName = blueprint.serviceName; + sessionName = blueprint.sessionName; + connectionSpec = blueprint.connectionSpec; + target = null; + } + @Override public boolean equals(Object obj) { diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java index abd33d6c9c2..a666a03c401 100755 --- a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCServicePool.java @@ -1,6 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.messagebus.network.rpc; +import com.yahoo.concurrent.CopyOnWriteHashMap; + import java.util.LinkedHashMap; import java.util.Map; @@ -12,7 +14,7 @@ import java.util.Map; public class RPCServicePool { private final RPCNetwork net; - private final ThreadLocalCache services = new ThreadLocalCache(); + private final Map<Long, ServiceLRUCache> mapOfServiceCache; private final int maxSize; /** @@ -23,6 +25,7 @@ public class RPCServicePool { */ public RPCServicePool(RPCNetwork net, int maxSize) { this.net = net; + mapOfServiceCache = new CopyOnWriteHashMap<>(); this.maxSize = maxSize; } @@ -34,12 +37,12 @@ public class RPCServicePool { * @return A service address for the given pattern. */ public RPCServiceAddress resolve(String pattern) { - RPCService service = services.get().get(pattern); - if (service == null) { - service = new RPCService(net.getMirror(), pattern); - services.get().put(pattern, service); - } - return service.resolve(); + + return getPerThreadCache().computeIfAbsent(pattern, (key) -> RPCService.create(net.getMirror(), key)).resolve(); + } + + private ServiceLRUCache getPerThreadCache() { + return mapOfServiceCache.computeIfAbsent(Thread.currentThread().getId(), (key) -> new ServiceLRUCache(maxSize)); } /** @@ -49,7 +52,7 @@ public class RPCServicePool { * @return The current size of this pool. */ public int getSize() { - return services.get().size(); + return getPerThreadCache().size(); } /** @@ -59,21 +62,15 @@ public class RPCServicePool { * @return True if a corresponding service is in the pool. */ public boolean hasService(String pattern) { - return services.get().containsKey(pattern); - } - - private class ThreadLocalCache extends ThreadLocal<ServiceLRUCache> { - - @Override - protected ServiceLRUCache initialValue() { - return new ServiceLRUCache(); - } + return getPerThreadCache().containsKey(pattern); } - private class ServiceLRUCache extends LinkedHashMap<String, RPCService> { + private static class ServiceLRUCache extends LinkedHashMap<String, RPCService> { + private final int maxSize; - ServiceLRUCache() { + ServiceLRUCache(int maxSize) { super(16, 0.75f, true); + this.maxSize = maxSize; } @Override diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/TcpRPCService.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/TcpRPCService.java new file mode 100644 index 00000000000..e2fae59b429 --- /dev/null +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/TcpRPCService.java @@ -0,0 +1,23 @@ +package com.yahoo.messagebus.network.rpc; + +public class TcpRPCService implements RPCService { + private final RPCServiceAddress blueprint; + + TcpRPCService(String pattern) { + if ( ! pattern.startsWith("tcp/")) { + throw new IllegalArgumentException("Expect tcp adress to start with 'tcp/', was: " + pattern); + } + RPCServiceAddress ret = null; + int pos = pattern.lastIndexOf('/'); + if (pos > 0 && pos < pattern.length() - 1) { + ret = new RPCServiceAddress(pattern, pattern.substring(0, pos)); + if ( ret.isMalformed()) { + ret = null; + } + } + blueprint = ret; + } + public RPCServiceAddress resolve() { + return blueprint != null ? new RPCServiceAddress(blueprint) : null; + } +} diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java b/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java index 3bf754f29c7..6451a692fb6 100644 --- a/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java +++ b/messagebus/src/test/java/com/yahoo/messagebus/network/NetworkMultiplexerTest.java @@ -86,7 +86,11 @@ public class NetworkMultiplexerTest { shared.detach(owner2); assertFalse(net.shutDown.get()); - shared.destroy(); + shared.attach(owner2); + shared.disown(); + assertFalse(net.shutDown.get()); + + shared.detach(owner2); assertTrue(net.shutDown.get()); } diff --git a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java index 1dbb30de585..0343b075579 100755 --- a/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java +++ b/messagebus/src/test/java/com/yahoo/messagebus/network/rpc/ServiceAddressTestCase.java @@ -83,11 +83,11 @@ public class ServiceAddressTestCase { } private void assertNullAddress(String pattern) { - assertNull(new RPCService(network.getMirror(), pattern).resolve()); + assertNull(RPCService.create(network.getMirror(), pattern).resolve()); } private void assertAddress(String pattern, String expectedSpec, String expectedSession) { - RPCService service = new RPCService(network.getMirror(), pattern); + RPCService service = RPCService.create(network.getMirror(), pattern); RPCServiceAddress obj = service.resolve(); assertNotNull(obj); assertNotNull(obj.getConnectionSpec()); diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/HttpMetricFetcher.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/HttpMetricFetcher.java index b4c116d1903..117503b95dc 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/HttpMetricFetcher.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/HttpMetricFetcher.java @@ -32,7 +32,7 @@ import java.util.logging.Logger; */ public abstract class HttpMetricFetcher { - private final static Logger log = Logger.getLogger(HttpMetricFetcher.class.getPackage().getName()); + private final static Logger log = Logger.getLogger(HttpMetricFetcher.class.getName()); public final static String STATE_PATH = "/state/v1/"; // The call to apache will do 3 retries. As long as we check the services in series, we can't have this too high. public static int CONNECTION_TIMEOUT = 5000; diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/RemoteHealthMetricFetcher.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/RemoteHealthMetricFetcher.java index 59db14670aa..03e72ec36de 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/RemoteHealthMetricFetcher.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/RemoteHealthMetricFetcher.java @@ -5,7 +5,6 @@ import ai.vespa.metricsproxy.metric.HealthMetric; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.concurrent.ExecutionException; import java.util.logging.Level; @@ -36,19 +35,19 @@ public class RemoteHealthMetricFetcher extends HttpMetricFetcher { return createHealthMetrics(stream, fetchCount); } catch (IOException | InterruptedException | ExecutionException e) { logMessageNoResponse(errMsgNoResponse(e), fetchCount); - byte [] empty = {'{','}'}; - return createHealthMetrics(new ByteArrayInputStream(empty), fetchCount); + return HealthMetric.getUnknown("Failed fetching metrics for service: " + service.getMonitoringName()); } } /** * Connect to remote service over http and fetch metrics */ - private HealthMetric createHealthMetrics(InputStream data, int fetchCount) { + private HealthMetric createHealthMetrics(InputStream data, int fetchCount) throws IOException { try { return parse(data); } catch (Exception e) { handleException(e, data, fetchCount); + while (data.read() != -1) {} return HealthMetric.getDown("Failed fetching status page for service"); } } diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/RemoteMetricsFetcher.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/RemoteMetricsFetcher.java index 3ee1e05c263..aad2f816959 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/RemoteMetricsFetcher.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/RemoteMetricsFetcher.java @@ -35,11 +35,12 @@ public class RemoteMetricsFetcher extends HttpMetricFetcher { handleException(e, data, fetchCount); } } - private void createMetrics(InputStream data, MetricsParser.Consumer consumer, int fetchCount) { + private void createMetrics(InputStream data, MetricsParser.Consumer consumer, int fetchCount) throws IOException { try { MetricsParser.parse(data, consumer); } catch (Exception e) { handleException(e, data, fetchCount); + while (data.read() != -1) {} } } } diff --git a/metrics/src/vespa/metrics/metricmanager.h b/metrics/src/vespa/metrics/metricmanager.h index f5ad5c5eea3..15f81126669 100644 --- a/metrics/src/vespa/metrics/metricmanager.h +++ b/metrics/src/vespa/metrics/metricmanager.h @@ -43,7 +43,7 @@ */ #pragma once -#include "config-metricsmanager.h" +#include <vespa/metrics/config-metricsmanager.h> #include "metricset.h" #include "metricsnapshot.h" #include "memoryconsumption.h" diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeAttributes.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeAttributes.java index 2379659f74b..32336743bdc 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeAttributes.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeAttributes.java @@ -6,9 +6,11 @@ import com.yahoo.component.Version; import com.yahoo.config.provision.DockerImage; import java.time.Instant; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -29,6 +31,7 @@ public class NodeAttributes { private Optional<Version> vespaVersion = Optional.empty(); private Optional<Version> currentOsVersion = Optional.empty(); private Optional<Instant> currentFirmwareCheck = Optional.empty(); + private List<TrustStoreItem> trustStore = List.of(); /** The list of reports to patch. A null value is used to remove the report. */ private Map<String, JsonNode> reports = new TreeMap<>(); @@ -144,7 +147,17 @@ public class NodeAttributes { && Objects.equals(vespaVersion, other.vespaVersion) && Objects.equals(currentOsVersion, other.currentOsVersion) && Objects.equals(currentFirmwareCheck, other.currentFirmwareCheck) - && Objects.equals(reports, other.reports); + && Objects.equals(reports, other.reports) + && Objects.equals(trustStore, other.trustStore); + } + + public NodeAttributes withTrustStore(List<TrustStoreItem> trustStore) { + this.trustStore = List.copyOf(trustStore); + return this; + } + + public List<TrustStoreItem> getTrustStore() { + return trustStore; } @Override @@ -156,7 +169,8 @@ public class NodeAttributes { vespaVersion.map(ver -> "vespaVersion=" + ver.toFullString()), currentOsVersion.map(ver -> "currentOsVersion=" + ver.toFullString()), currentFirmwareCheck.map(at -> "currentFirmwareCheck=" + at), - Optional.ofNullable(reports.isEmpty() ? null : "reports=" + reports)) + Optional.ofNullable(reports.isEmpty() ? null : "reports=" + reports), + Optional.ofNullable(trustStore.isEmpty() ? null : "trustStore=" + trustStore)) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.joining(", ", "{", "}")); diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeSpec.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeSpec.java index e85d51ef992..4000a0ac182 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeSpec.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeSpec.java @@ -68,6 +68,8 @@ public class NodeSpec { private final Optional<ApplicationId> exclusiveTo; + private final List<TrustStoreItem> trustStore; + public NodeSpec( String hostname, Optional<String> id, @@ -98,7 +100,8 @@ public class NodeSpec { List<Event> events, Optional<String> parentHostname, Optional<URI> archiveUri, - Optional<ApplicationId> exclusiveTo) { + Optional<ApplicationId> exclusiveTo, + List<TrustStoreItem> trustStore) { if (state == NodeState.active) { requireOptional(owner, "owner"); requireOptional(membership, "membership"); @@ -138,6 +141,7 @@ public class NodeSpec { this.parentHostname = Objects.requireNonNull(parentHostname); this.archiveUri = Objects.requireNonNull(archiveUri); this.exclusiveTo = Objects.requireNonNull(exclusiveTo); + this.trustStore = Objects.requireNonNull(trustStore); } public String hostname() { @@ -283,6 +287,10 @@ public class NodeSpec { return exclusiveTo; } + public List<TrustStoreItem> trustStore() { + return trustStore; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -319,7 +327,8 @@ public class NodeSpec { Objects.equals(events, that.events) && Objects.equals(parentHostname, that.parentHostname) && Objects.equals(archiveUri, that.archiveUri) && - Objects.equals(exclusiveTo, that.exclusiveTo); + Objects.equals(exclusiveTo, that.exclusiveTo) && + Objects.equals(trustStore, that.trustStore); } @Override @@ -354,7 +363,8 @@ public class NodeSpec { events, parentHostname, archiveUri, - exclusiveTo); + exclusiveTo, + trustStore); } @Override @@ -390,6 +400,7 @@ public class NodeSpec { + " parentHostname=" + parentHostname + " archiveUri=" + archiveUri + " exclusiveTo=" + exclusiveTo + + " trustStore=" + trustStore + " }"; } @@ -424,6 +435,7 @@ public class NodeSpec { private Optional<String> parentHostname = Optional.empty(); private Optional<URI> archiveUri = Optional.empty(); private Optional<ApplicationId> exclusiveTo = Optional.empty(); + private List<TrustStoreItem> trustStore = List.of(); public Builder() {} @@ -456,6 +468,7 @@ public class NodeSpec { node.parentHostname.ifPresent(this::parentHostname); node.archiveUri.ifPresent(this::archiveUri); node.exclusiveTo.ifPresent(this::exclusiveTo); + trustStore(node.trustStore); } public Builder hostname(String hostname) { @@ -633,12 +646,19 @@ public class NodeSpec { return this; } + public Builder trustStore(List<TrustStoreItem> trustStore) { + this.trustStore = List.copyOf(trustStore); + return this; + } + public Builder updateFromNodeAttributes(NodeAttributes attributes) { attributes.getHostId().ifPresent(this::id); attributes.getDockerImage().ifPresent(this::currentDockerImage); attributes.getCurrentOsVersion().ifPresent(this::currentOsVersion); attributes.getRebootGeneration().ifPresent(this::currentRebootGeneration); attributes.getRestartGeneration().ifPresent(this::currentRestartGeneration); + // Always replace entire trust store + trustStore(attributes.getTrustStore()); this.reports.updateFromRawMap(attributes.getReports()); return this; @@ -752,7 +772,7 @@ public class NodeSpec { wantedRebootGeneration, currentRebootGeneration, wantedFirmwareCheck, currentFirmwareCheck, modelName, resources, realResources, ipAddresses, additionalIpAddresses, - reports, events, parentHostname, archiveUri, exclusiveTo); + reports, events, parentHostname, archiveUri, exclusiveTo, trustStore); } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java index abc779d8a9a..d5e3acf0656 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java @@ -155,6 +155,11 @@ public class RealNodeRepository implements NodeRepository { .map(event -> new Event(event.agent, event.event, Optional.ofNullable(event.at).map(Instant::ofEpochMilli).orElse(Instant.EPOCH))) .collect(Collectors.toUnmodifiableList()); + List<TrustStoreItem> trustStore = Optional.ofNullable(node.trustStore).orElse(List.of()).stream() + .map(item -> new TrustStoreItem(item.fingerprint, Instant.ofEpochMilli(item.expiry))) + .collect(Collectors.toList()); + + return new NodeSpec( node.hostname, Optional.ofNullable(node.openStackId), @@ -185,7 +190,8 @@ public class RealNodeRepository implements NodeRepository { events, Optional.ofNullable(node.parentHostname), Optional.ofNullable(node.archiveUri).map(URI::create), - Optional.ofNullable(node.exclusiveTo).map(ApplicationId::fromSerializedForm)); + Optional.ofNullable(node.exclusiveTo).map(ApplicationId::fromSerializedForm), + trustStore); } private static NodeResources nodeResources(NodeRepositoryNode.NodeResources nodeResources) { @@ -270,7 +276,9 @@ public class RealNodeRepository implements NodeRepository { node.vespaVersion = nodeAttributes.getVespaVersion().map(Version::toFullString).orElse(null); node.currentOsVersion = nodeAttributes.getCurrentOsVersion().map(Version::toFullString).orElse(null); node.currentFirmwareCheck = nodeAttributes.getCurrentFirmwareCheck().map(Instant::toEpochMilli).orElse(null); - + node.trustStore = nodeAttributes.getTrustStore().stream() + .map(item -> new NodeRepositoryNode.TrustStoreItem(item.fingerprint(), item.expiry().toEpochMilli())) + .collect(Collectors.toList()); Map<String, JsonNode> reports = nodeAttributes.getReports(); node.reports = reports == null || reports.isEmpty() ? null : new TreeMap<>(reports); diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/TrustStoreItem.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/TrustStoreItem.java new file mode 100644 index 00000000000..d3e797bca24 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/TrustStoreItem.java @@ -0,0 +1,48 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.vespa.hosted.node.admin.configserver.noderepository; + +import java.time.Instant; +import java.util.Objects; + +/** + * @author mortent + */ +public class TrustStoreItem { + private final String fingerprint; + private final Instant expiry; + + public TrustStoreItem(String fingerprint, Instant expiry) { + this.fingerprint = fingerprint; + this.expiry = expiry; + } + + public String fingerprint() { + return fingerprint; + } + + public Instant expiry() { + return expiry; + } + + @Override + public String toString() { + return "TrustStoreItem{" + + "fingerprint='" + fingerprint + '\'' + + ", expiry=" + expiry + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TrustStoreItem that = (TrustStoreItem) o; + return Objects.equals(fingerprint, that.fingerprint) && Objects.equals(expiry, that.expiry); + } + + @Override + public int hashCode() { + return Objects.hash(fingerprint, expiry); + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeRepositoryNode.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeRepositoryNode.java index 4282c67b4cd..f471f9a9965 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeRepositoryNode.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeRepositoryNode.java @@ -85,6 +85,9 @@ public class NodeRepositoryNode { public String exclusiveTo; @JsonProperty("history") public List<Event> history; + @JsonProperty("trustStore") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public List<TrustStoreItem> trustStore; @JsonProperty("reports") public Map<String, JsonNode> reports = null; @@ -221,4 +224,17 @@ public class NodeRepositoryNode { '}'; } } + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class TrustStoreItem { + @JsonProperty ("fingerprint") + public String fingerprint; + @JsonProperty ("expiry") + public long expiry; + + public TrustStoreItem(@JsonProperty("fingerprint") String fingerprint, @JsonProperty("expiry") long expiry) { + this.fingerprint = fingerprint; + this.expiry = expiry; + } + } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java index 240e041a504..df3ac00ce7f 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/Node.java @@ -18,13 +18,16 @@ import com.yahoo.vespa.hosted.provision.node.IP; import com.yahoo.vespa.hosted.provision.node.NodeAcl; import com.yahoo.vespa.hosted.provision.node.Reports; import com.yahoo.vespa.hosted.provision.node.Status; +import com.yahoo.vespa.hosted.provision.node.TrustStoreItem; import java.time.Instant; import java.util.Arrays; import java.util.EnumSet; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; /** * A node in the node repository. The identity of a node is given by its id. @@ -50,6 +53,7 @@ public final class Node implements Nodelike { private final Optional<ApplicationId> exclusiveToApplicationId; private final Optional<ClusterSpec.Type> exclusiveToClusterType; private final Optional<String> switchHostname; + private final List<TrustStoreItem> trustStoreItems; /** Record of the last event of each type happening to this node */ private final History history; @@ -79,7 +83,7 @@ public final class Node implements Nodelike { Flavor flavor, Status status, State state, Optional<Allocation> allocation, History history, NodeType type, Reports reports, Optional<String> modelName, Optional<TenantName> reservedTo, Optional<ApplicationId> exclusiveToApplicationId, Optional<ClusterSpec.Type> exclusiveToClusterType, - Optional<String> switchHostname) { + Optional<String> switchHostname, List<TrustStoreItem> trustStoreItems) { this.id = Objects.requireNonNull(id, "A node must have an ID"); this.hostname = requireNonEmptyString(hostname, "A node must have a hostname"); this.ipConfig = Objects.requireNonNull(ipConfig, "A node must a have an IP config"); @@ -96,6 +100,7 @@ public final class Node implements Nodelike { this.exclusiveToApplicationId = Objects.requireNonNull(exclusiveToApplicationId, "exclusiveToApplicationId cannot be null"); this.exclusiveToClusterType = Objects.requireNonNull(exclusiveToClusterType, "exclusiveToClusterType cannot be null"); this.switchHostname = requireNonEmptyString(switchHostname, "switchHostname cannot be null"); + this.trustStoreItems = trustStoreItems.stream().distinct().collect(Collectors.toUnmodifiableList()); if (state == State.active) requireNonEmpty(ipConfig.primary(), "Active node " + hostname + " must have at least one valid IP address"); @@ -207,6 +212,11 @@ public final class Node implements Nodelike { return switchHostname; } + /** Returns the trusted certificates for this host if any. */ + public List<TrustStoreItem> trustedCertificates() { + return trustStoreItems; + } + /** * Returns a copy of this where wantToFail is set to true and history is updated to reflect this. */ @@ -295,13 +305,13 @@ public final class Node implements Nodelike { /** Returns a node with the status assigned to the given value */ public Node with(Status status) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type, - reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } /** Returns a node with the type assigned to the given value */ public Node with(NodeType type) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, allocation, history, type, - reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } /** Returns a node with the flavor assigned to the given value */ @@ -309,31 +319,31 @@ public final class Node implements Nodelike { if (flavor.equals(this.flavor)) return this; History updateHistory = history.with(new History.Event(History.Event.Type.resized, agent, instant)); return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, allocation, updateHistory, type, - reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } /** Returns a copy of this with the reboot generation set to generation */ public Node withReboot(Generation generation) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status.withReboot(generation), state, - allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } /** Returns a copy of this with the openStackId set */ public Node withOpenStackId(String openStackId) { return new Node(openStackId, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } /** Returns a copy of this with model name set to given value */ public Node withModelName(String modelName) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, Optional.of(modelName), reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + allocation, history, type, reports, Optional.of(modelName), reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } /** Returns a copy of this with model name cleared */ public Node withoutModelName() { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, Optional.empty(), reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + allocation, history, type, reports, Optional.empty(), reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } /** Returns a copy of this with a history record saying it was detected to be down at this instant */ @@ -364,55 +374,55 @@ public final class Node implements Nodelike { */ public Node with(Allocation allocation) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - Optional.of(allocation), history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + Optional.of(allocation), history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } /** Returns a new Node without an allocation. */ public Node withoutAllocation() { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - Optional.empty(), history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + Optional.empty(), history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } /** Returns a copy of this node with IP config set to the given value. */ public Node with(IP.Config ipConfig) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } /** Returns a copy of this node with the parent hostname assigned to the given value. */ public Node withParentHostname(String parentHostname) { return new Node(id, ipConfig, hostname, Optional.of(parentHostname), flavor, status, state, - allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } public Node withReservedTo(TenantName tenant) { if (type != NodeType.host) throw new IllegalArgumentException("Only host nodes can be reserved, " + hostname + " has type " + type); return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, Optional.of(tenant), exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + allocation, history, type, reports, modelName, Optional.of(tenant), exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } /** Returns a copy of this node which is not reserved to a tenant */ public Node withoutReservedTo() { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, Optional.empty(), exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + allocation, history, type, reports, modelName, Optional.empty(), exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } public Node withExclusiveToApplicationId(ApplicationId exclusiveTo) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo, Optional.ofNullable(exclusiveTo), exclusiveToClusterType, switchHostname); + allocation, history, type, reports, modelName, reservedTo, Optional.ofNullable(exclusiveTo), exclusiveToClusterType, switchHostname, trustStoreItems); } public Node withExclusiveToClusterType(ClusterSpec.Type exclusiveTo) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, Optional.ofNullable(exclusiveTo), switchHostname); + allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, Optional.ofNullable(exclusiveTo), switchHostname, trustStoreItems); } /** Returns a copy of this node with switch hostname set to given value */ public Node withSwitchHostname(String switchHostname) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, Optional.ofNullable(switchHostname)); + allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, Optional.ofNullable(switchHostname), trustStoreItems); } /** Returns a copy of this node with switch hostname unset */ @@ -450,12 +460,18 @@ public final class Node implements Nodelike { /** Returns a copy of this node with the given history. */ public Node with(History history) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); } public Node with(Reports reports) { return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, - allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname); + allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, trustStoreItems); + } + + public Node with(List<TrustStoreItem> trustStoreItems) { + return new Node(id, ipConfig, hostname, parentHostname, flavor, status, state, + allocation, history, type, reports, modelName, reservedTo, exclusiveToApplicationId, exclusiveToClusterType, switchHostname, + trustStoreItems); } private static Optional<String> requireNonEmptyString(Optional<String> value, String message) { @@ -594,6 +610,7 @@ public final class Node implements Nodelike { private Status status; private Reports reports; private History history; + private List<TrustStoreItem> trustStoreItems; private Builder(String id, String hostname, Flavor flavor, State state, NodeType type) { this.id = id; @@ -663,12 +680,18 @@ public final class Node implements Nodelike { return this; } + public Builder trustedCertificates(List<TrustStoreItem> trustStoreItems) { + this.trustStoreItems = trustStoreItems; + return this; + } + public Node build() { return new Node(id, Optional.ofNullable(ipConfig).orElse(IP.Config.EMPTY), hostname, Optional.ofNullable(parentHostname), - flavor, Optional.ofNullable(status).orElseGet(Status::initial), state, Optional.ofNullable(allocation), - Optional.ofNullable(history).orElseGet(History::empty), type, Optional.ofNullable(reports).orElseGet(Reports::new), - Optional.ofNullable(modelName), Optional.ofNullable(reservedTo), Optional.ofNullable(exclusiveToApplicationId), - Optional.ofNullable(exclusiveToClusterType), Optional.ofNullable(switchHostname)); + flavor, Optional.ofNullable(status).orElseGet(Status::initial), state, Optional.ofNullable(allocation), + Optional.ofNullable(history).orElseGet(History::empty), type, Optional.ofNullable(reports).orElseGet(Reports::new), + Optional.ofNullable(modelName), Optional.ofNullable(reservedTo), Optional.ofNullable(exclusiveToApplicationId), + Optional.ofNullable(exclusiveToClusterType), Optional.ofNullable(switchHostname), + Optional.ofNullable(trustStoreItems).orElseGet(List::of)); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/TrustStoreItem.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/TrustStoreItem.java new file mode 100644 index 00000000000..6fb94d0bc62 --- /dev/null +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/TrustStoreItem.java @@ -0,0 +1,68 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.vespa.hosted.provision.node; + +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Inspector; + +import java.time.Instant; +import java.util.Objects; + +/** + * Contains the fingerprint and expiry of certificates in a hosts truststore. + * + * @author mortent + */ +public class TrustStoreItem { + private static final String FINGERPRINT_FIELD = "fingerprint"; + private static final String EXPIRY_FIELD = "expiry"; + + private final String fingerprint; + private final Instant expiry; + + public TrustStoreItem(String fingerprint, Instant expiry) { + this.fingerprint = fingerprint; + this.expiry = expiry; + } + + public String fingerprint() { + return fingerprint; + } + + public Instant expiry() { + return expiry; + } + + public void toSlime(Cursor trustedCertificatesRoot) { + Cursor object = trustedCertificatesRoot.addObject(); + object.setString(FINGERPRINT_FIELD, fingerprint); + object.setLong(EXPIRY_FIELD, expiry.toEpochMilli()); + } + + public static TrustStoreItem fromSlime(Inspector inspector) { + String fingerprint = inspector.field(FINGERPRINT_FIELD).asString(); + Instant expiry = Instant.ofEpochMilli(inspector.field(EXPIRY_FIELD).asLong()); + return new TrustStoreItem(fingerprint, expiry); + } + + @Override + public String toString() { + return "TrustedCertificate{" + + "fingerprint='" + fingerprint + '\'' + + ", expiry=" + expiry + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TrustStoreItem that = (TrustStoreItem) o; + return Objects.equals(fingerprint, that.fingerprint) && Objects.equals(expiry, that.expiry); + } + + @Override + public int hashCode() { + return Objects.hash(fingerprint, expiry); + } +}
\ No newline at end of file diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java index d5a4c459ef3..7e252391cb3 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java @@ -210,7 +210,7 @@ public class CuratorDatabaseClient { toState.isAllocated() ? node.allocation() : Optional.empty(), node.history().recordStateTransition(node.state(), toState, agent, clock.instant()), node.type(), node.reports(), node.modelName(), node.reservedTo(), - node.exclusiveToApplicationId(), node.exclusiveToClusterType(), node.switchHostname()); + node.exclusiveToApplicationId(), node.exclusiveToClusterType(), node.switchHostname(), node.trustedCertificates()); writeNode(toState, curatorTransaction, node, newNode); writtenNodes.add(newNode); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java index 19bbe92eff6..868837daeeb 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializer.java @@ -36,6 +36,7 @@ import com.yahoo.vespa.hosted.provision.node.IP; import com.yahoo.vespa.hosted.provision.node.OsVersion; import com.yahoo.vespa.hosted.provision.node.Reports; import com.yahoo.vespa.hosted.provision.node.Status; +import com.yahoo.vespa.hosted.provision.node.TrustStoreItem; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -95,6 +96,7 @@ public class NodeSerializer { private static final String exclusiveToApplicationIdKey = "exclusiveTo"; private static final String exclusiveToClusterTypeKey = "exclusiveToClusterType"; private static final String switchHostnameKey = "switchHostname"; + private static final String trustedCertificatesKey = "trustedCertificates"; // Node resource fields private static final String flavorKey = "flavor"; @@ -122,6 +124,10 @@ public class NodeSerializer { // Network port fields private static final String networkPortsKey = "networkPorts"; + // Trusted certificates fields + private static final String fingerprintKey = "fingerprint"; + private static final String expiresKey = "expires"; + // A cache of deserialized Node objects. The cache is keyed on the hash of serialized node data. // // Deserializing a Node from slime is expensive, and happens frequently. Node instances that have already been @@ -182,6 +188,7 @@ public class NodeSerializer { node.reservedTo().ifPresent(tenant -> object.setString(reservedToKey, tenant.value())); node.exclusiveToApplicationId().ifPresent(applicationId -> object.setString(exclusiveToApplicationIdKey, applicationId.serializedForm())); node.exclusiveToClusterType().ifPresent(clusterType -> object.setString(exclusiveToClusterTypeKey, clusterType.name())); + trustedCertificatesToSlime(node.trustedCertificates(), object.setArray(trustedCertificatesKey)); } private void toSlime(Flavor flavor, Cursor object) { @@ -236,6 +243,14 @@ public class NodeSerializer { }); } + private void trustedCertificatesToSlime(List<TrustStoreItem> trustStoreItems, Cursor array) { + trustStoreItems.forEach(cert -> { + Cursor object = array.addObject(); + object.setString(fingerprintKey, cert.fingerprint()); + object.setLong(expiresKey, cert.expiry().toEpochMilli()); + }); + } + // ---------------- Deserialization -------------------------------------------------- public Node fromJson(Node.State state, byte[] data) { @@ -269,7 +284,8 @@ public class NodeSerializer { reservedToFromSlime(object.field(reservedToKey)), exclusiveToApplicationIdFromSlime(object.field(exclusiveToApplicationIdKey)), exclusiveToClusterTypeFromSlime(object.field(exclusiveToClusterTypeKey)), - switchHostnameFromSlime(object.field(switchHostnameKey))); + switchHostnameFromSlime(object.field(switchHostnameKey)), + trustedCertificatesFromSlime(object)); } private Status statusFromSlime(Inspector object) { @@ -419,6 +435,13 @@ public class NodeSerializer { return Optional.of(ClusterSpec.Type.from(object.asString())); } + private List<TrustStoreItem> trustedCertificatesFromSlime(Inspector object) { + return SlimeUtils.entriesStream(object.field(trustedCertificatesKey)) + .map(elem -> new TrustStoreItem(elem.field(fingerprintKey).asString(), + Instant.ofEpochMilli(elem.field(expiresKey).asLong()))) + .collect(Collectors.toList()); + } + // ----------------- Enum <-> string mappings ---------------------------------------- /** Returns the event type, or null if this event type should be ignored */ diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java index 8d37c13d2bc..5dfacc2c3d2 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java @@ -25,6 +25,7 @@ import com.yahoo.vespa.hosted.provision.node.Allocation; import com.yahoo.vespa.hosted.provision.node.IP; import com.yahoo.vespa.hosted.provision.node.Report; import com.yahoo.vespa.hosted.provision.node.Reports; +import com.yahoo.vespa.hosted.provision.node.TrustStoreItem; import java.io.IOException; import java.io.InputStream; @@ -186,6 +187,8 @@ public class NodePatcher implements AutoCloseable { return node.withExclusiveToClusterType(SlimeUtils.optionalString(value).map(ClusterSpec.Type::valueOf).orElse(null)); case "switchHostname": return value.type() == Type.NIX ? node.withoutSwitchHostname() : node.withSwitchHostname(value.asString()); + case "trustStore": + return nodeWithTrustStore(node, value); default : throw new IllegalArgumentException("Could not apply field '" + name + "' on a node: No such modifiable field"); } @@ -230,6 +233,14 @@ public class NodePatcher implements AutoCloseable { return patchedNode; } + private Node nodeWithTrustStore(Node node, Inspector inspector) { + List<TrustStoreItem> trustStoreItems = + SlimeUtils.entriesStream(inspector) + .map(TrustStoreItem::fromSlime) + .collect(Collectors.toList()); + return node.with(trustStoreItems); + } + private Set<String> asStringSet(Inspector field) { if ( ! field.type().equals(Type.ARRAY)) throw new IllegalArgumentException("Expected an ARRAY value, got a " + field.type()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java index 2cf671514c4..80100128379 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesResponse.java @@ -16,6 +16,7 @@ import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Address; import com.yahoo.vespa.hosted.provision.node.History; +import com.yahoo.vespa.hosted.provision.node.TrustStoreItem; import com.yahoo.vespa.orchestrator.Orchestrator; import com.yahoo.vespa.orchestrator.status.HostInfo; import com.yahoo.vespa.orchestrator.status.HostStatus; @@ -182,6 +183,7 @@ class NodesResponse extends SlimeJsonResponse { node.modelName().ifPresent(modelName -> object.setString("modelName", modelName)); node.switchHostname().ifPresent(switchHostname -> object.setString("switchHostname", switchHostname)); nodeRepository.archiveUris().archiveUriFor(node).ifPresent(uri -> object.setString("archiveUri", uri)); + trustedCertsToSlime(node.trustedCertificates(), object); } private void toSlime(ApplicationId id, Cursor object) { @@ -228,6 +230,12 @@ class NodesResponse extends SlimeJsonResponse { addresses.forEach(address -> addressesArray.addString(address.hostname())); } + private void trustedCertsToSlime(List<TrustStoreItem> trustStoreItems, Cursor object) { + if (trustStoreItems.isEmpty()) return; + Cursor array = object.setArray("trustStore"); + trustStoreItems.forEach(cert -> cert.toSlime(array)); + } + private String lastElement(String path) { if (path.endsWith("/")) path = path.substring(0, path.length()-1); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java index 158a1d6e5ac..921b78252d3 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/NodeSerializerTest.java @@ -29,6 +29,7 @@ import com.yahoo.vespa.hosted.provision.node.History; import com.yahoo.vespa.hosted.provision.node.IP; import com.yahoo.vespa.hosted.provision.node.Report; import com.yahoo.vespa.hosted.provision.node.Reports; +import com.yahoo.vespa.hosted.provision.node.TrustStoreItem; import com.yahoo.vespa.hosted.provision.provisioning.FlavorConfigBuilder; import org.junit.Test; @@ -462,6 +463,16 @@ public class NodeSerializerTest { assertEquals(exclusiveToCluster, node.exclusiveToClusterType().get()); } + @Test + public void truststore_serialization() { + Node node = nodeSerializer.fromJson(State.active, nodeSerializer.toJson(createNode())); + assertEquals(List.of(), node.trustedCertificates()); + List<TrustStoreItem> trustStoreItems = List.of(new TrustStoreItem("foo", Instant.parse("2023-09-01T23:59:59Z")), new TrustStoreItem("bar", Instant.parse("2025-05-20T23:59:59Z"))); + node = node.with(trustStoreItems); + node = nodeSerializer.fromJson(State.active, nodeSerializer.toJson(node)); + assertEquals(trustStoreItems, node.trustedCertificates()); + } + private byte[] createNodeJson(String hostname, String... ipAddress) { String ipAddressJsonPart = ""; if (ipAddress.length > 0) { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java index fa68533bb06..4c9597d00bd 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java @@ -43,7 +43,6 @@ import com.yahoo.vespa.hosted.provision.node.filter.NodeHostFilter; import com.yahoo.vespa.hosted.provision.persistence.NameResolver; import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver; import com.yahoo.vespa.hosted.provision.testutils.MockProvisionServiceProvider; -import com.yahoo.vespa.orchestrator.OrchestrationException; import com.yahoo.vespa.orchestrator.Orchestrator; import com.yahoo.vespa.service.duper.ConfigServerApplication; @@ -687,11 +686,7 @@ public class ProvisioningTester { Orchestrator orchestrator = Optional.ofNullable(this.orchestrator) .orElseGet(() -> { Orchestrator orch = mock(Orchestrator.class); - try { - doThrow(new RuntimeException()).when(orch).acquirePermissionToRemove(any()); - } catch (OrchestrationException e) { - throw new RuntimeException(e); - } + doThrow(new RuntimeException()).when(orch).acquirePermissionToRemove(any()); return orch; }); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java index 6c052a6c364..88a8a22913e 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java @@ -1023,6 +1023,26 @@ public class NodesV2ApiTest { tester.assertPartialResponse(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com"), "archiveUri", false); } + @Test + public void trusted_certificates_patch() throws IOException { + String url = "http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com"; + tester.assertPartialResponse(new Request(url), "\"trustStore\":[]", false); // initially empty list + + String trustStore = "\"trustStore\":[" + + "{" + + "\"fingerprint\":\"foo\"," + + "\"expiry\":1632302251000" + + "}," + + "{" + + "\"fingerprint\":\"bar\"," + + "\"expiry\":1758532706000" + + "}" + + "]"; + assertResponse(new Request(url, Utf8.toBytes("{"+trustStore+"}"), Request.Method.PATCH), + "{\"message\":\"Updated dockerhost1.yahoo.com\"}"); + tester.assertPartialResponse(new Request(url), trustStore, true); + } + private static String asDockerNodeJson(String hostname, String parentHostname, String... ipAddress) { return asDockerNodeJson(hostname, NodeType.tenant, parentHostname, ipAddress); } diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestrationException.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestrationException.java index 95d862d9e72..f2eae278150 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestrationException.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/OrchestrationException.java @@ -3,7 +3,7 @@ package com.yahoo.vespa.orchestrator; import java.util.Arrays; -public class OrchestrationException extends Exception { +public class OrchestrationException extends RuntimeException { public OrchestrationException(Throwable cause) { super(cause); diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApi.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApi.java index fd115702588..47ba6dedd84 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApi.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApi.java @@ -17,13 +17,20 @@ public interface ClusterApi { String clusterInfo(); ClusterId clusterId(); ServiceType serviceType(); + + /** Some human-readable string naming the service(s) to a human reader. */ + String serviceDescription(boolean plural); + boolean isStorageCluster(); - /** Returns the reasons no services are up in the implied group, or empty if some services are up. */ - Optional<SuspensionReasons> reasonsForNoServicesInGroupIsUp(); + boolean isConfigServerLike(); + + /** Returns the non-empty reasons for why all services are down, or otherwise empty. */ + Optional<SuspensionReasons> allServicesDown(); + boolean noServicesOutsideGroupIsDown() throws HostStateChangeDeniedException; - int percentageOfServicesDown(); + int percentageOfServicesDownOutsideGroup(); int percentageOfServicesDownIfGroupIsAllowedToBeDown(); Optional<StorageNode> storageNodeInGroup(); diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApiImpl.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApiImpl.java index 00cb65a09b0..6880700e4a9 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApiImpl.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/model/ClusterApiImpl.java @@ -19,7 +19,6 @@ import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.Map; import java.util.Optional; @@ -41,9 +40,10 @@ class ClusterApiImpl implements ClusterApi { private final ClusterControllerClientFactory clusterControllerClientFactory; private final Clock clock; private final Set<ServiceInstance> servicesInGroup; - private final Set<ServiceInstance> servicesDownInGroup; private final Set<ServiceInstance> servicesNotInGroup; - private final Set<ServiceInstance> servicesDownAndNotInGroup; + + /** Lazily initialized in servicesDownAndNotInGroup(), do not access directly. */ + private Set<ServiceInstance> servicesDownAndNotInGroup = null; /* * There are two sources for the number of config servers in a cluster. The config server config and the node @@ -81,9 +81,6 @@ class ClusterApiImpl implements ClusterApi { servicesInGroup = serviceInstancesByLocality.getOrDefault(true, Collections.emptySet()); servicesNotInGroup = serviceInstancesByLocality.getOrDefault(false, Collections.emptySet()); - servicesDownInGroup = servicesInGroup.stream().filter(this::serviceEffectivelyDown).collect(Collectors.toSet()); - servicesDownAndNotInGroup = servicesNotInGroup.stream().filter(this::serviceEffectivelyDown).collect(Collectors.toSet()); - int serviceInstances = serviceCluster.serviceInstances().size(); if (clusterParams.size().isPresent() && serviceInstances < clusterParams.size().getAsInt()) { missingServices = clusterParams.size().getAsInt() - serviceInstances; @@ -110,6 +107,11 @@ class ClusterApiImpl implements ClusterApi { } @Override + public String serviceDescription(boolean plural) { + return serviceCluster.serviceDescription(plural); + } + + @Override public boolean isStorageCluster() { return VespaModelUtil.isStorage(serviceCluster); } @@ -120,7 +122,12 @@ class ClusterApiImpl implements ClusterApi { } @Override - public Optional<SuspensionReasons> reasonsForNoServicesInGroupIsUp() { + public boolean isConfigServerLike() { + return serviceCluster.isConfigServerLike(); + } + + @Override + public Optional<SuspensionReasons> allServicesDown() { SuspensionReasons reasons = new SuspensionReasons(); for (ServiceInstance service : servicesInGroup) { @@ -156,29 +163,18 @@ class ClusterApiImpl implements ClusterApi { @Override public boolean noServicesOutsideGroupIsDown() throws HostStateChangeDeniedException { - Optional<ServiceInstance> serviceWithUnknownStatus = servicesNotInGroup - .stream() - .filter(serviceInstance -> serviceInstance.serviceStatus() == ServiceStatus.UNKNOWN) - .min(Comparator.comparing(ServiceInstance::descriptiveName)); - if (serviceWithUnknownStatus.isPresent()) { - throw new HostStateChangeDeniedException( - nodeGroup, - HostedVespaPolicy.UNKNOWN_SERVICE_STATUS, - "Service status of " + serviceWithUnknownStatus.get().descriptiveName() + " is not yet known"); - } - - return servicesDownAndNotInGroup.size() + missingServices == 0; + return servicesDownAndNotInGroup().size() + missingServices == 0; } @Override - public int percentageOfServicesDown() { - int numberOfServicesDown = servicesDownAndNotInGroup.size() + missingServices + servicesDownInGroup.size(); + public int percentageOfServicesDownOutsideGroup() { + int numberOfServicesDown = servicesDownAndNotInGroup().size() + missingServices; return numberOfServicesDown * 100 / (serviceCluster.serviceInstances().size() + missingServices); } @Override public int percentageOfServicesDownIfGroupIsAllowedToBeDown() { - int numberOfServicesDown = servicesDownAndNotInGroup.size() + missingServices + servicesInGroup.size(); + int numberOfServicesDown = servicesDownAndNotInGroup().size() + missingServices + servicesInGroup.size(); return numberOfServicesDown * 100 / (serviceCluster.serviceInstances().size() + missingServices); } @@ -200,15 +196,14 @@ class ClusterApiImpl implements ClusterApi { description.append(" "); final int nodeLimit = 3; - description.append("Suspended hosts: "); description.append(suspended.stream().sorted().distinct().limit(nodeLimit).collect(Collectors.toList()).toString()); if (suspended.size() > nodeLimit) { - description.append(", and " + (suspended.size() - nodeLimit) + " more"); + description.append(" and " + (suspended.size() - nodeLimit) + " others"); } - description.append("."); + description.append(" are suspended."); } - Set<ServiceInstance> downElsewhere = servicesDownAndNotInGroup.stream() + Set<ServiceInstance> downElsewhere = servicesDownAndNotInGroup().stream() .filter(serviceInstance -> !suspended.contains(serviceInstance.hostName())) .collect(Collectors.toSet()); @@ -217,7 +212,6 @@ class ClusterApiImpl implements ClusterApi { description.append(" "); final int serviceLimit = 2; // services info is verbose - description.append("Services down on resumed hosts: "); description.append(Stream.concat( downElsewhere.stream().map(ServiceInstance::toString).sorted(), missingServices > 0 ? Stream.of(descriptionOfMissingServices) : Stream.of()) @@ -226,9 +220,9 @@ class ClusterApiImpl implements ClusterApi { .toString()); if (downElsewhereTotal > serviceLimit) { - description.append(", and " + (downElsewhereTotal - serviceLimit) + " more"); + description.append(" and " + (downElsewhereTotal - serviceLimit) + " others"); } - description.append("."); + description.append(" are down."); } return description.toString(); @@ -288,19 +282,31 @@ class ClusterApiImpl implements ClusterApi { return "{ clusterId=" + clusterId() + ", serviceType=" + serviceType() + " }"; } + private Set<ServiceInstance> servicesDownAndNotInGroup() { + if (servicesDownAndNotInGroup == null) { + servicesDownAndNotInGroup = servicesNotInGroup.stream().filter(this::serviceEffectivelyDown).collect(Collectors.toSet()); + } + return servicesDownAndNotInGroup; + } + private HostStatus hostStatus(HostName hostName) { return hostInfos.getOrNoRemarks(hostName).status(); } - private boolean serviceEffectivelyDown(ServiceInstance service) { + private boolean serviceEffectivelyDown(ServiceInstance service) throws HostStateChangeDeniedException { if (hostStatus(service.hostName()).isSuspended()) { return true; } - if (service.serviceStatus() == ServiceStatus.DOWN) { - return true; + switch (service.serviceStatus()) { + case DOWN: return true; + case UNKNOWN: + throw new HostStateChangeDeniedException( + nodeGroup, + HostedVespaPolicy.UNKNOWN_SERVICE_STATUS, + "Service status of " + service.descriptiveName() + " is not yet known"); + default: + return false; } - - return false; } } diff --git a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/HostedVespaClusterPolicy.java b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/HostedVespaClusterPolicy.java index 208e12690ff..fe06ef7d75b 100644 --- a/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/HostedVespaClusterPolicy.java +++ b/orchestrator/src/main/java/com/yahoo/vespa/orchestrator/policy/HostedVespaClusterPolicy.java @@ -23,6 +23,16 @@ public class HostedVespaClusterPolicy implements ClusterPolicy { @Override public SuspensionReasons verifyGroupGoingDownIsFine(ClusterApi clusterApi) throws HostStateChangeDeniedException { + return verifyGroupGoingDownIsFine(clusterApi, false); + } + + @Override + public void verifyGroupGoingDownPermanentlyIsFine(ClusterApi clusterApi) throws HostStateChangeDeniedException { + verifyGroupGoingDownIsFine(clusterApi, true); + } + + private SuspensionReasons verifyGroupGoingDownIsFine(ClusterApi clusterApi, boolean permanent) + throws HostStateChangeDeniedException { if (clusterApi.noServicesOutsideGroupIsDown()) { return SuspensionReasons.nothingNoteworthy(); } @@ -32,47 +42,28 @@ public class HostedVespaClusterPolicy implements ClusterPolicy { return SuspensionReasons.nothingNoteworthy(); } - Optional<SuspensionReasons> suspensionReasons = clusterApi.reasonsForNoServicesInGroupIsUp(); - if (suspensionReasons.isPresent()) { - return suspensionReasons.get(); + // Be a bit more cautious when removing nodes permanently + if (!permanent) { + // Disallow suspending a 2nd and downed config server to avoid losing ZK quorum. + if (!clusterApi.isConfigServerLike()) { + Optional<SuspensionReasons> suspensionReasons = clusterApi.allServicesDown(); + if (suspensionReasons.isPresent()) { + return suspensionReasons.get(); + } + } } String message = percentageOfServicesAllowedToBeDown <= 0 - ? "Suspension of service with type '" + clusterApi.serviceType() + "' not allowed: " - + clusterApi.percentageOfServicesDown() + "% are suspended already." + clusterApi.downDescription() - : "Suspension of service with type '" + clusterApi.serviceType() - + "' would increase from " + clusterApi.percentageOfServicesDown() - + "% to " + clusterApi.percentageOfServicesDownIfGroupIsAllowedToBeDown() - + "%, over the limit of " + percentageOfServicesAllowedToBeDown + "%." - + clusterApi.downDescription(); + ? clusterApi.percentageOfServicesDownOutsideGroup() + "% of the " + clusterApi.serviceDescription(true) + + " are down or suspended already:" + clusterApi.downDescription() + : "The percentage of downed or suspended " + clusterApi.serviceDescription(true) + + " would increase from " + clusterApi.percentageOfServicesDownOutsideGroup() + "% to " + + clusterApi.percentageOfServicesDownIfGroupIsAllowedToBeDown() + "% (limit is " + + percentageOfServicesAllowedToBeDown + "%):" + clusterApi.downDescription(); throw new HostStateChangeDeniedException(clusterApi.getNodeGroup(), ENOUGH_SERVICES_UP_CONSTRAINT, message); } - @Override - public void verifyGroupGoingDownPermanentlyIsFine(ClusterApi clusterApi) throws HostStateChangeDeniedException { - // This policy is similar to verifyGroupGoingDownIsFine, except that having no services up in the group will - // not allow the suspension: We are a bit more cautious when removing nodes. - - if (clusterApi.noServicesOutsideGroupIsDown()) { - return; - } - - int percentageOfServicesAllowedToBeDown = getConcurrentSuspensionLimit(clusterApi) - .asPercentage(); - if (clusterApi.percentageOfServicesDownIfGroupIsAllowedToBeDown() <= percentageOfServicesAllowedToBeDown) { - return; - } - - throw new HostStateChangeDeniedException( - clusterApi.getNodeGroup(), - ENOUGH_SERVICES_UP_CONSTRAINT, - "Down percentage for service type " + clusterApi.serviceType() - + " would increase to " + clusterApi.percentageOfServicesDownIfGroupIsAllowedToBeDown() - + "%, over the limit of " + percentageOfServicesAllowedToBeDown + "%." - + clusterApi.downDescription()); - } - // Non-private for testing purposes ConcurrentSuspensionLimitForCluster getConcurrentSuspensionLimit(ClusterApi clusterApi) { // Possible service clusters on a node as of 2021-01-22: diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/OrchestratorTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/OrchestratorTest.java index 83c902ed981..f885d5301de 100644 --- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/OrchestratorTest.java +++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/OrchestratorTest.java @@ -102,7 +102,7 @@ public class OrchestratorTest { fail(); } catch (HostStateChangeDeniedException e) { assertThat(e.getMessage(), containsString("Changing the state of cfg2 would violate enough-services-up")); - assertThat(e.getMessage(), containsString("Suspended hosts: [cfg1]")); + assertThat(e.getMessage(), containsString("[cfg1] are suspended.")); } // cfg1 is removed from the application @@ -114,7 +114,7 @@ public class OrchestratorTest { fail(); } catch (HostStateChangeDeniedException e) { assertThat(e.getMessage(), containsString("Changing the state of cfg2 would violate enough-services-up")); - assertThat(e.getMessage(), containsString("Services down on resumed hosts: [1 missing config server]")); + assertThat(e.getMessage(), containsString("[1 missing config server] are down.")); } // cfg1 is reprovisioned, added to the node repo, and activated @@ -129,7 +129,7 @@ public class OrchestratorTest { fail(); } catch (HostStateChangeDeniedException e) { assertThat(e.getMessage(), containsString("Changing the state of cfg1 would violate enough-services-up")); - assertThat(e.getMessage(), containsString("Suspended hosts: [cfg2]")); + assertThat(e.getMessage(), containsString("[cfg2] are suspended")); } // etc (should be the same as for cfg1) diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ClusterApiImplTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ClusterApiImplTest.java index da8591c6631..3ea739b385e 100644 --- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ClusterApiImplTest.java +++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/model/ClusterApiImplTest.java @@ -95,11 +95,11 @@ public class ClusterApiImplTest { assertEquals("{ clusterId=cluster, serviceType=service-type }", clusterApi.clusterInfo()); assertFalse(clusterApi.isStorageCluster()); - assertEquals(" Suspended hosts: [host3, host4]. Services down on resumed hosts: [" + - "ServiceInstance{configId=service-2, hostName=host2, serviceStatus=" + - "ServiceStatusInfo{status=DOWN, since=Optional.empty, lastChecked=Optional.empty}}].", + assertEquals(" [host3, host4] are suspended. [ServiceInstance{configId=service-2, hostName=host2, " + + "serviceStatus=ServiceStatusInfo{status=DOWN, since=Optional.empty, lastChecked=Optional.empty}}] " + + "are down.", clusterApi.downDescription()); - assertEquals(60, clusterApi.percentageOfServicesDown()); + assertEquals(60, clusterApi.percentageOfServicesDownOutsideGroup()); assertEquals(80, clusterApi.percentageOfServicesDownIfGroupIsAllowedToBeDown()); } @@ -181,8 +181,8 @@ public class ClusterApiImplTest { fail(); } catch (HostStateChangeDeniedException e) { assertThat(e.getMessage(), - containsString("Suspension of service with type 'configserver' not allowed: 33% are suspended already. " + - "Services down on resumed hosts: [1 missing config server].")); + containsString("Changing the state of cfg1 would violate enough-services-up: 33% of the config " + + "servers are down or suspended already: [1 missing config server] are down.")); } } @@ -201,18 +201,25 @@ public class ClusterApiImplTest { fail(); } catch (HostStateChangeDeniedException e) { assertThat(e.getMessage(), - containsString("Suspension of service with type 'hostadmin' not allowed: 33% are suspended already. " + - "Services down on resumed hosts: [1 missing config server host].")); + containsString("Changing the state of cfg1 would violate enough-services-up: 33% of the config " + + "server hosts are down or suspended already: [1 missing config server host] are down.")); } } @Test - public void testCfg1SuspendsIfDownWithMissingCfg3() throws HostStateChangeDeniedException { + public void testCfg1DoesNotSuspendIfDownWithMissingCfg3() throws HostStateChangeDeniedException { ClusterApiImpl clusterApi = makeCfg1ClusterApi(ServiceStatus.DOWN, ServiceStatus.UP); HostedVespaClusterPolicy policy = new HostedVespaClusterPolicy(flagSource, zone); - policy.verifyGroupGoingDownIsFine(clusterApi); + try { + policy.verifyGroupGoingDownIsFine(clusterApi); + fail(); + } catch (HostStateChangeDeniedException e) { + assertThat(e.getMessage(), + containsString("Changing the state of cfg1 would violate enough-services-up: 33% of the config " + + "servers are down or suspended already: [1 missing config server] are down.")); + } } @Test @@ -320,7 +327,7 @@ public class ClusterApiImplTest { clock); assertEquals(expectedNoServicesInGroupIsUp.map(SuspensionReasons::getMessagesInOrder), - clusterApi.reasonsForNoServicesInGroupIsUp().map(SuspensionReasons::getMessagesInOrder)); + clusterApi.allServicesDown().map(SuspensionReasons::getMessagesInOrder)); assertEquals(expectedNoServicesOutsideGroupIsDown, clusterApi.noServicesOutsideGroupIsDown()); } diff --git a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/policy/HostedVespaClusterPolicyTest.java b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/policy/HostedVespaClusterPolicyTest.java index 303dabebba8..8f47051621f 100644 --- a/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/policy/HostedVespaClusterPolicyTest.java +++ b/orchestrator/src/test/java/com/yahoo/vespa/orchestrator/policy/HostedVespaClusterPolicyTest.java @@ -131,13 +131,14 @@ public class HostedVespaClusterPolicyTest { int percentageOfServicesDownIfGroupIsAllowedToBeDown, boolean expectSuccess) throws HostStateChangeDeniedException { when(clusterApi.noServicesOutsideGroupIsDown()).thenReturn(noServicesOutsideGroupIsDown); - when(clusterApi.reasonsForNoServicesInGroupIsUp()).thenReturn(noServicesInGroupIsUp); + when(clusterApi.allServicesDown()).thenReturn(noServicesInGroupIsUp); when(clusterApi.percentageOfServicesDownIfGroupIsAllowedToBeDown()).thenReturn(20); doReturn(ConcurrentSuspensionLimitForCluster.TEN_PERCENT).when(policy).getConcurrentSuspensionLimit(clusterApi); when(applicationApi.applicationId()).thenReturn(ApplicationId.fromSerializedForm("a:b:c")); when(clusterApi.serviceType()).thenReturn(new ServiceType("service-type")); - when(clusterApi.percentageOfServicesDown()).thenReturn(5); + when(clusterApi.serviceDescription(true)).thenReturn("services of {service-type,cluster-id}"); + when(clusterApi.percentageOfServicesDownOutsideGroup()).thenReturn(5); when(clusterApi.percentageOfServicesDownIfGroupIsAllowedToBeDown()).thenReturn(percentageOfServicesDownIfGroupIsAllowedToBeDown); when(clusterApi.downDescription()).thenReturn(" Down description"); @@ -152,9 +153,10 @@ public class HostedVespaClusterPolicyTest { } } catch (HostStateChangeDeniedException e) { if (!expectSuccess) { - assertEquals("Changing the state of node-group would violate enough-services-up: " + - "Suspension of service with type 'service-type' would increase from 5% to 13%, " + - "over the limit of 10%. Down description", e.getMessage()); + assertEquals("Changing the state of node-group would violate enough-services-up: The percentage of downed " + + "or suspended services of {service-type,cluster-id} would increase from 5% to 13% (limit is 10%): " + + "Down description", + e.getMessage()); assertEquals("enough-services-up", e.getConstraintName()); } } diff --git a/parent/pom.xml b/parent/pom.xml index ca61f6707e7..fa04c167d0e 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -543,18 +543,6 @@ <version>${prometheus.client.version}</version> </dependency> <dependency> - <!-- TODO: Try to remove, as this overlaps with javax.activation. --> - <groupId>jakarta.activation</groupId> - <artifactId>jakarta.activation-api</artifactId> - <version>1.2.1</version> - </dependency> - <dependency> - <!-- TODO: Try to remove, as this conflicts with javax.xml.bind:jaxb-api --> - <groupId>jakarta.xml.bind</groupId> - <artifactId>jakarta.xml.bind-api</artifactId> - <version>2.3.2</version> - </dependency> - <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> diff --git a/searchcore/CMakeLists.txt b/searchcore/CMakeLists.txt index c0353747e80..36ef347b7ac 100644 --- a/searchcore/CMakeLists.txt +++ b/searchcore/CMakeLists.txt @@ -54,6 +54,7 @@ vespa_define_module( src/apps/vespa-transactionlog-inspect TESTS + src/tests/bmcluster/estimate_moved_docs_ratio src/tests/grouping src/tests/proton/attribute src/tests/proton/attribute/attribute_aspect_delayer diff --git a/searchcore/src/apps/vespa-redistribute-bm/vespa_redistribute_bm.cpp b/searchcore/src/apps/vespa-redistribute-bm/vespa_redistribute_bm.cpp index e5c3959d2d4..0227d9539d2 100644 --- a/searchcore/src/apps/vespa-redistribute-bm/vespa_redistribute_bm.cpp +++ b/searchcore/src/apps/vespa-redistribute-bm/vespa_redistribute_bm.cpp @@ -17,6 +17,8 @@ #include <vespa/searchcore/bmcluster/bm_node_stats_reporter.h> #include <vespa/searchcore/bmcluster/bm_range.h> #include <vespa/searchcore/bmcluster/bucket_selector.h> +#include <vespa/searchcore/bmcluster/calculate_moved_docs_ratio.h> +#include <vespa/searchcore/bmcluster/estimate_moved_docs_ratio.h> #include <vespa/searchcore/bmcluster/spi_bm_feed_handler.h> #include <vespa/searchlib/index/dummyfileheadercontext.h> #include <vespa/vespalib/io/fileutil.h> @@ -51,6 +53,8 @@ using search::bmcluster::BmNode; using search::bmcluster::BmNodeStatsReporter; using search::bmcluster::BmRange; using search::bmcluster::BucketSelector; +using search::bmcluster::CalculateMovedDocsRatio; +using search::bmcluster::EstimateMovedDocsRatio; using search::index::DummyFileHeaderContext; using storage::lib::State; @@ -100,76 +104,6 @@ vespalib::string& get_mode_name(Mode mode) { return (i < mode_names.size()) ? mode_names[i] : bad_mode_name; } -double -estimate_lost_docs_base_ratio(uint32_t redundancy, uint32_t lost_nodes, uint32_t num_nodes) -{ - if (redundancy > lost_nodes) { - return 0.0; - } - double loss_ratio = 1.0; - for (uint32_t i = 0; i < redundancy; ++i) { - loss_ratio *= ((double) (lost_nodes - i)) / (num_nodes - i); - } - LOG(info, "estimated lost docs base ratio: %4.2f", loss_ratio); - return loss_ratio; -} - -double -estimate_moved_docs_ratio_grow(uint32_t redundancy, uint32_t added_nodes, uint32_t num_nodes) -{ - double new_redundancy = redundancy; - double new_per_node_doc_ratio = new_redundancy / num_nodes; - double moved_ratio = new_per_node_doc_ratio * added_nodes; - LOG(info, "estimated_moved_docs_ratio_grow(%u,%u,%u)=%4.2f", redundancy, added_nodes, num_nodes, moved_ratio); - return moved_ratio; -} - -double -estimate_moved_docs_ratio_shrink(uint32_t redundancy, uint32_t retired_nodes, uint32_t num_nodes) -{ - double old_redundancy = redundancy; - double old_per_node_doc_ratio = old_redundancy / num_nodes; - uint32_t new_nodes = num_nodes - retired_nodes; - double new_redundancy = std::min(redundancy, new_nodes); - double new_per_node_doc_ratio = new_redundancy / new_nodes; - double moved_ratio = (new_per_node_doc_ratio - old_per_node_doc_ratio) * new_nodes; - LOG(info, "estimated_moved_docs_ratio_shrink(%u,%u,%u)=%4.2f", redundancy, retired_nodes, num_nodes, moved_ratio); - return moved_ratio; -} - -double -estimate_moved_docs_ratio_crash(uint32_t redundancy, uint32_t crashed_nodes, uint32_t num_nodes) -{ - double old_redundancy = redundancy; - double old_per_node_doc_ratio = old_redundancy / num_nodes; - uint32_t new_nodes = num_nodes - crashed_nodes; - double new_redundancy = std::min(redundancy, new_nodes); - double new_per_node_doc_ratio = new_redundancy / new_nodes; - double lost_docs_ratio = estimate_lost_docs_base_ratio(redundancy, crashed_nodes, num_nodes) * new_redundancy; - double moved_ratio = (new_per_node_doc_ratio - old_per_node_doc_ratio) * new_nodes - lost_docs_ratio; - LOG(info, "estimated_moved_docs_ratio_crash(%u,%u,%u)=%4.2f", redundancy, crashed_nodes, num_nodes, moved_ratio); - return moved_ratio; -} - -double -estimate_moved_docs_ratio_replace(uint32_t redundancy, uint32_t added_nodes, uint32_t retired_nodes, uint32_t num_nodes) -{ - uint32_t old_nodes = num_nodes - added_nodes; - double old_redundancy = std::min(redundancy, old_nodes); - double old_per_node_doc_ratio = old_redundancy / old_nodes; - uint32_t new_nodes = num_nodes - retired_nodes; - double new_redundancy = std::min(redundancy, new_nodes); - double new_per_node_doc_ratio = new_redundancy / new_nodes; - double moved_ratio = new_per_node_doc_ratio * added_nodes; - uint32_t stable_nodes = num_nodes - added_nodes - retired_nodes; - // Account for extra documents moved from retired nodes to stable nodes - double extra_per_stable_node_doc_ratio = new_per_node_doc_ratio * added_nodes / old_nodes; - double extra_moved_ratio = (std::min(1.0, new_per_node_doc_ratio + extra_per_stable_node_doc_ratio) - old_per_node_doc_ratio) * stable_nodes; - moved_ratio += extra_moved_ratio; - LOG(info, "estimated_moved_docs_ratio_replace(%u,%u,%u,%u)=%4.2f, (of which %4.2f extra)", redundancy, added_nodes, retired_nodes, num_nodes, moved_ratio, extra_moved_ratio); - return moved_ratio; -} - class BMParams : public BmClusterParams, public BmFeedParams { @@ -391,7 +325,7 @@ Benchmark::estimate_lost_docs() case Mode::TEMP_CRASH: { double new_redundancy = std::min(_params.get_redundancy(), _params.get_num_nodes() - _params.get_flip_nodes()); - auto lost_docs_ratio = estimate_lost_docs_base_ratio(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()) * new_redundancy; + auto lost_docs_ratio = EstimateMovedDocsRatio().estimate_lost_docs_base_ratio(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()) * new_redundancy; return _params.get_documents() * lost_docs_ratio; } default: @@ -404,14 +338,21 @@ Benchmark::estimate_moved_docs() { switch(_params.get_mode()) { case Mode::GROW: - return _params.get_documents() * estimate_moved_docs_ratio_grow(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); + return _params.get_documents() * EstimateMovedDocsRatio().estimate_moved_docs_ratio_grow(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); case Mode::SHRINK: - return _params.get_documents() * estimate_moved_docs_ratio_shrink(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); + return _params.get_documents() * EstimateMovedDocsRatio().estimate_moved_docs_ratio_shrink(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); case Mode::PERM_CRASH: case Mode::TEMP_CRASH: - return _params.get_documents() * estimate_moved_docs_ratio_crash(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); + return _params.get_documents() * EstimateMovedDocsRatio().estimate_moved_docs_ratio_crash(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_num_nodes()); case Mode::REPLACE: - return _params.get_documents() * estimate_moved_docs_ratio_replace(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_flip_nodes(), _params.get_num_nodes()); + if (_params.get_num_nodes() < 10) { + // Calculate better estimate for moved docs ratio with brute force + auto scanner = CalculateMovedDocsRatio::make_replace_calculator(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_flip_nodes(), _params.get_num_nodes()); + scanner.scan(); + return _params.get_documents() * scanner.get_moved_docs_ratio(); + } else { + return _params.get_documents() * EstimateMovedDocsRatio().estimate_moved_docs_ratio_replace(_params.get_redundancy(), _params.get_flip_nodes(), _params.get_flip_nodes(), _params.get_num_nodes()); + } default: return 0.0; } diff --git a/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/CMakeLists.txt b/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/CMakeLists.txt new file mode 100644 index 00000000000..23f72e4f1fa --- /dev/null +++ b/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(searchcore_bmcluster_estimate_moved_docs_ratio_test_app TEST + SOURCES + estimate_moved_docs_ratio_test.cpp + DEPENDS + searchcore_bmcluster + GTest::GTest +) + +vespa_add_test(NAME searchcore_bmcluster_estimate_moved_docs_ratio_test_app + COMMAND searchcore_bmcluster_estimate_moved_docs_ratio_test_app) diff --git a/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/estimate_moved_docs_ratio_test.cpp b/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/estimate_moved_docs_ratio_test.cpp new file mode 100644 index 00000000000..79af31e3247 --- /dev/null +++ b/searchcore/src/tests/bmcluster/estimate_moved_docs_ratio/estimate_moved_docs_ratio_test.cpp @@ -0,0 +1,134 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/gtest/gtest.h> +#include <vespa/searchcore/bmcluster/calculate_moved_docs_ratio.h> +#include <vespa/searchcore/bmcluster/estimate_moved_docs_ratio.h> +#include <iostream> + +#include <vespa/log/log.h> +LOG_SETUP("estimate_moved_docs_ratio_test"); + +using search::bmcluster::CalculateMovedDocsRatio; +using search::bmcluster::EstimateMovedDocsRatio; + +namespace { + +bool verbose; + +TEST(EstimateMovedDocsRatioTest, estimate_lost_docs_ratio) +{ + for (uint32_t nodes = 1; nodes < 2; ++nodes) { + for (uint32_t redundancy = 1; redundancy <= nodes; ++redundancy) { + for (uint32_t lost_nodes = 0; lost_nodes <= nodes; ++lost_nodes) { + auto scanner = CalculateMovedDocsRatio::make_crash_calculator(redundancy, lost_nodes, nodes); + scanner.scan(); + double lost_docs_base_ratio = scanner.get_lost_docs_base_ratio(); + double estimated_lost_docs_base_ratio = EstimateMovedDocsRatio().estimate_lost_docs_base_ratio(redundancy, lost_nodes, nodes); + EXPECT_DOUBLE_EQ(lost_docs_base_ratio, estimated_lost_docs_base_ratio); + } + } + } +} + +TEST(EstimateMovedDocsRatioTest, estimate_moved_docs_ratio_grow) +{ + for (uint32_t nodes = 1; nodes < 10; ++nodes) { + for (uint32_t redundancy = 1; redundancy <= nodes; ++redundancy) { + for (uint32_t added_nodes = 0; added_nodes <= nodes; ++added_nodes) { + auto scanner = CalculateMovedDocsRatio::make_grow_calculator(redundancy, added_nodes, nodes); + scanner.scan(); + double moved_docs_ratio = scanner.get_moved_docs_ratio(); + double estimated_moved_docs_ratio = EstimateMovedDocsRatio().estimate_moved_docs_ratio_grow(redundancy, added_nodes, nodes); + EXPECT_DOUBLE_EQ(moved_docs_ratio, estimated_moved_docs_ratio); + } + } + } +} + +TEST(EstimateMovedDocsRatioTest, estimate_moved_docs_ratio_shrink) +{ + for (uint32_t nodes = 1; nodes < 10; ++nodes) { + for (uint32_t redundancy = 1; redundancy <= nodes; ++redundancy) { + for (uint32_t retired_nodes = 0; retired_nodes <= nodes; ++retired_nodes) { + auto scanner = CalculateMovedDocsRatio::make_shrink_calculator(redundancy, retired_nodes, nodes); + scanner.scan(); + double moved_docs_ratio = scanner.get_moved_docs_ratio(); + double estimated_moved_docs_ratio = EstimateMovedDocsRatio().estimate_moved_docs_ratio_shrink(redundancy, retired_nodes, nodes); + EXPECT_DOUBLE_EQ(moved_docs_ratio, estimated_moved_docs_ratio); + } + } + } +} + +TEST(EstimateMovedDocsRatioTest, estimate_moved_docs_ratio_crash) +{ + double epsilon = 1e-15; + for (uint32_t nodes = 1; nodes < 10; ++nodes) { + for (uint32_t redundancy = 1; redundancy <= nodes; ++redundancy) { + for (uint32_t crashed_nodes = 0; crashed_nodes <= nodes; ++crashed_nodes) { + auto scanner = CalculateMovedDocsRatio::make_crash_calculator(redundancy, crashed_nodes, nodes); + scanner.scan(); + double moved_docs_ratio = scanner.get_moved_docs_ratio(); + double estimated_moved_docs_ratio = EstimateMovedDocsRatio().estimate_moved_docs_ratio_crash(redundancy, crashed_nodes, nodes); + EXPECT_NEAR(moved_docs_ratio, estimated_moved_docs_ratio, epsilon); + } + } + } +} + +TEST(EstimateMovedDocsRatioTest, estimate_moved_docs_ratio_replace) +{ + uint32_t bad_cases = 0; + uint32_t really_bad_cases = 0; + if (verbose) { + std::cout << "Summary: HDR Red A Ret N Act Est ScaleMv ScaleEs States" << std::endl; + } + for (uint32_t nodes = 1; nodes < 6; ++nodes) { + for (uint32_t redundancy = 1; redundancy <= nodes; ++redundancy) { + for (uint32_t retired_nodes = 0; retired_nodes <= nodes; ++retired_nodes) { + for (uint32_t added_nodes = 0; added_nodes <= nodes - retired_nodes; ++added_nodes) { + // std::cout << "Estimate moved docs ratio replace " << retired_nodes << " of " << nodes << " retired, added " << added_nodes << " nodes ,redundancy " << redundancy << std::endl; + auto scanner = CalculateMovedDocsRatio::make_replace_calculator(redundancy, added_nodes, retired_nodes, nodes); + scanner.scan(); + double moved_docs_ratio = scanner.get_moved_docs_ratio(); + double estimated_moved_docs_ratio = EstimateMovedDocsRatio(verbose).estimate_moved_docs_ratio_replace(redundancy, added_nodes, retired_nodes, nodes); + double error_ratio = abs(moved_docs_ratio - estimated_moved_docs_ratio); + bool bad = error_ratio > 1e-8; + bool really_bad = error_ratio > 0.2 * estimated_moved_docs_ratio + 1e-8; + if (bad) { + ++bad_cases; + } + if (really_bad) { + ++really_bad_cases; + } + if (verbose) { + double scaled_moved = moved_docs_ratio * scanner.get_checked_states(); + double scaled_estimated_moved = estimated_moved_docs_ratio * scanner.get_checked_states(); + std::cout << "Summary: " << (bad ? "BAD" : "OK ") << std::setw(4) << redundancy << std::setw(4) << added_nodes << std::setw(4) << retired_nodes << std::setw(4) << nodes << " " << std::setw(12) << std::setprecision(5) << std::fixed << moved_docs_ratio << " " << std::setw(12) << std::setprecision(5) << std::fixed << estimated_moved_docs_ratio << " " << std::setw(12) << std::setprecision(5) << std::fixed << scaled_moved << " " << std::setw(12) << std::setprecision(5) << std::fixed << scaled_estimated_moved << std::setw(8) << scanner.get_checked_states(); + std::cout << " ["; + for (uint32_t node_idx = 0; node_idx < nodes; ++node_idx) { + std::cout << std::setw(8) << scanner.get_moved_docs_per_node()[node_idx]; + } + std::cout << " ]" << std::endl; + } + // TODO: Fix calculation so we get zero bad cases. + // EXPECT_DOUBLE_EQ(moved_docs_ratio, estimated_moved_docs_ratio); + } + } + } + } + EXPECT_LE(6, bad_cases); + EXPECT_LE(1, really_bad_cases); +} + +} // namespace + +int +main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + if (argc > 1 && std::string("--verbose") == argv[1]) { + verbose = true; + } + return RUN_ALL_TESTS(); +} diff --git a/searchcore/src/tests/proton/server/disk_mem_usage_sampler/disk_mem_usage_sampler_test.cpp b/searchcore/src/tests/proton/server/disk_mem_usage_sampler/disk_mem_usage_sampler_test.cpp index b7f217290d6..1475ac7f9d7 100644 --- a/searchcore/src/tests/proton/server/disk_mem_usage_sampler/disk_mem_usage_sampler_test.cpp +++ b/searchcore/src/tests/proton/server/disk_mem_usage_sampler/disk_mem_usage_sampler_test.cpp @@ -62,7 +62,13 @@ TEST_F(DiskMemUsageSamplerTest, resource_usage_is_sampled) std::this_thread::sleep_for(50ms); } LOG(info, "Polled %zu times (%zu ms) to get a sample", i, i * 50); +#ifdef __linux__ + // Anonymous resident memory used by current process is sampled. EXPECT_GT(filter().getMemoryStats().getAnonymousRss(), 0); +#else + // Anonymous resident memory used by current process is not sampled. + EXPECT_EQ(filter().getMemoryStats().getAnonymousRss(), 0); +#endif EXPECT_GT(filter().getDiskUsedSize(), 0); EXPECT_EQ(100, filter().get_transient_memory_usage()); EXPECT_EQ(100.0 / memory_size_bytes, filter().get_relative_transient_memory_usage()); diff --git a/searchcore/src/vespa/searchcore/bmcluster/CMakeLists.txt b/searchcore/src/vespa/searchcore/bmcluster/CMakeLists.txt index d2ab8b0c2a9..0de021a9514 100644 --- a/searchcore/src/vespa/searchcore/bmcluster/CMakeLists.txt +++ b/searchcore/src/vespa/searchcore/bmcluster/CMakeLists.txt @@ -19,7 +19,9 @@ vespa_add_library(searchcore_bmcluster STATIC bm_storage_link.cpp bm_storage_message_addresses.cpp bucket_info_queue.cpp + calculate_moved_docs_ratio.cpp document_api_message_bus_bm_feed_handler.cpp + estimate_moved_docs_ratio.cpp pending_tracker.cpp pending_tracker_hash.cpp spi_bm_feed_handler.cpp diff --git a/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.cpp b/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.cpp new file mode 100644 index 00000000000..63f23476a95 --- /dev/null +++ b/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.cpp @@ -0,0 +1,142 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "calculate_moved_docs_ratio.h" +#include <cassert> + +namespace search::bmcluster { + +namespace { + +uint32_t make_bit_range(uint32_t low, uint32_t high) +{ + return (1u << high) - (1u << low); +} + +} + +struct CalculateMovedDocsRatio::Placements +{ + uint32_t _mask; + uint32_t _count; + + Placements() + : _mask(0u), + _count(0u) + { + } + + Placements(uint32_t mask, uint32_t count) noexcept + : _mask(mask), + _count(count) + { + } + + Placements add(uint32_t idx) const noexcept { + return Placements(_mask | (1u << idx), _count + 1); + } + + Placements add(uint32_t idx, uint32_t mask, uint32_t redundancy) const noexcept { + return (((_count < redundancy) && ((1u << idx) & mask) != 0)) ? Placements(_mask | (1u << idx), _count + 1) : *this; + } +}; + + +CalculateMovedDocsRatio::CalculateMovedDocsRatio(uint32_t nodes, uint32_t redundancy, uint32_t old_placement_mask, uint32_t new_placement_mask, uint32_t new_up_mask) + : _num_states(nodes + 1), + _nodes(nodes), + _old_placement_mask(old_placement_mask), + _new_placement_mask(new_placement_mask), + _new_up_mask(new_up_mask), + _moved_docs(0u), + _moved_docs_per_node(nodes), + _checked_states(0u), + _lost_docs_base(0u), + _redundancy(redundancy), + _old_redundancy(std::min(redundancy, (uint32_t)__builtin_popcount(old_placement_mask))), + _new_redundancy(std::min(redundancy, (uint32_t)__builtin_popcount(new_placement_mask))) +{ + assert((new_placement_mask & ~new_up_mask) == 0u); + uint32_t states = 1; + for (uint32_t level = nodes; level > 0; --level) { + states *= std::max(1u, nodes - level); + _num_states[level] = states; + } + _num_states[0] = states * nodes; +} + +CalculateMovedDocsRatio::~CalculateMovedDocsRatio() = default; + +CalculateMovedDocsRatio +CalculateMovedDocsRatio::make_grow_calculator(uint32_t redundancy, uint32_t added_nodes, uint32_t nodes) +{ + uint32_t old_placement_mask = make_bit_range(added_nodes, nodes); + uint32_t new_placement_mask = make_bit_range(0, nodes); + return CalculateMovedDocsRatio(nodes, redundancy, old_placement_mask, new_placement_mask, new_placement_mask); +} + +CalculateMovedDocsRatio +CalculateMovedDocsRatio::make_shrink_calculator(uint32_t redundancy, uint32_t retired_nodes, uint32_t nodes) +{ + uint32_t old_placement_mask = make_bit_range(0, nodes); + uint32_t new_placement_mask = make_bit_range(retired_nodes, nodes); + return CalculateMovedDocsRatio(nodes, redundancy, old_placement_mask, new_placement_mask, old_placement_mask); +} + +CalculateMovedDocsRatio +CalculateMovedDocsRatio::make_crash_calculator(uint32_t redundancy, uint32_t crashed_nodes, uint32_t nodes) +{ + uint32_t old_placement_mask = make_bit_range(0, nodes); + uint32_t new_placement_mask = make_bit_range(crashed_nodes, nodes); + return CalculateMovedDocsRatio(nodes, redundancy, old_placement_mask, new_placement_mask, new_placement_mask); + +} + +CalculateMovedDocsRatio +CalculateMovedDocsRatio::make_replace_calculator(uint32_t redundancy, uint32_t added_nodes, uint32_t retired_nodes, uint32_t nodes) +{ + uint32_t old_placement_mask = make_bit_range(added_nodes, nodes); + uint32_t new_placement_mask = make_bit_range(added_nodes + retired_nodes, nodes) | make_bit_range(0, added_nodes); + uint32_t new_up_mask = make_bit_range(0, nodes); + return CalculateMovedDocsRatio(nodes, redundancy, old_placement_mask, new_placement_mask, new_up_mask); +} + +void +CalculateMovedDocsRatio::scan(Placements selected, Placements old_placement, Placements new_placement) +{ + if (old_placement._count >= _old_redundancy) { + if ((old_placement._mask & _new_up_mask) == 0) { + _lost_docs_base += _num_states[selected._count]; + _checked_states += _num_states[selected._count]; + return; + } + if (new_placement._count >= _new_redundancy) { + _checked_states += _num_states[selected._count]; + uint32_t only_new_mask = new_placement._mask & ~old_placement._mask; + if (only_new_mask != 0) { + _moved_docs += _num_states[selected._count] * (uint32_t)__builtin_popcount(only_new_mask); + for (uint32_t node_idx = 0; node_idx < _nodes; ++node_idx) { + if ((only_new_mask & (1u << node_idx)) != 0) { + _moved_docs_per_node[node_idx] += _num_states[selected._count]; + } + } + } + return; + } + } + assert(selected._count < _nodes); + for (uint32_t node_idx = 0; node_idx < _nodes; ++node_idx) { + if ((selected._mask & (1u << node_idx)) != 0) { + continue; + } + scan(selected.add(node_idx), old_placement.add(node_idx, _old_placement_mask, _old_redundancy), new_placement.add(node_idx, _new_placement_mask, _new_redundancy)); + } +} + +void +CalculateMovedDocsRatio::scan() +{ + scan(Placements(), Placements(), Placements()); + assert(_checked_states == _num_states[0]); +} + +} diff --git a/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.h b/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.h new file mode 100644 index 00000000000..cd161177246 --- /dev/null +++ b/searchcore/src/vespa/searchcore/bmcluster/calculate_moved_docs_ratio.h @@ -0,0 +1,48 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <cstdint> +#include <vector> + +namespace search::bmcluster { + +/* + * Class for calculating moved docs ratio during + * document redistribution. + */ +class CalculateMovedDocsRatio +{ + class Placements; + std::vector<uint32_t> _num_states; + uint32_t _nodes; + uint32_t _old_placement_mask; + uint32_t _new_placement_mask; + uint32_t _new_up_mask; + uint32_t _moved_docs; + std::vector<uint32_t> _moved_docs_per_node; + uint32_t _checked_states; + uint32_t _lost_docs_base; + uint32_t _redundancy; + uint32_t _old_redundancy; + uint32_t _new_redundancy; + + void scan(Placements selected, Placements old_placement, Placements new_placement); +public: + CalculateMovedDocsRatio(uint32_t nodes, uint32_t redundancy, uint32_t old_placement_mask, uint32_t new_placement_mask, uint32_t new_up_mask); + ~CalculateMovedDocsRatio(); + static CalculateMovedDocsRatio make_grow_calculator(uint32_t redundancy, uint32_t added_nodes, uint32_t nodes); + static CalculateMovedDocsRatio make_shrink_calculator(uint32_t redundancy, uint32_t retired_nodes, uint32_t nodes); + static CalculateMovedDocsRatio make_crash_calculator(uint32_t redundancy, uint32_t crashed_nodes, uint32_t nodes); + static CalculateMovedDocsRatio make_replace_calculator(uint32_t redundancy, uint32_t added_nodes, uint32_t retired_nodes, uint32_t nodes); + void scan(); + uint32_t get_lost_docs_base() const noexcept { return _lost_docs_base; } + uint32_t get_checked_states() const noexcept { return _checked_states; } + uint32_t get_new_redundancy() const noexcept { return _new_redundancy; } + uint32_t get_moved_docs() const noexcept { return _moved_docs; } + const std::vector<uint32_t>& get_moved_docs_per_node() const noexcept { return _moved_docs_per_node; } + double get_lost_docs_base_ratio() const noexcept { return ((double) _lost_docs_base) / _checked_states; } + double get_moved_docs_ratio() const noexcept { return ((double) _moved_docs) / _checked_states; } +}; + +} diff --git a/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.cpp b/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.cpp new file mode 100644 index 00000000000..cffa6fc79e9 --- /dev/null +++ b/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.cpp @@ -0,0 +1,114 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "estimate_moved_docs_ratio.h" + +#include <vespa/log/log.h> +LOG_SETUP(".bmcluster.estimate_moved_docs_ratio"); + +namespace search::bmcluster { + +EstimateMovedDocsRatio::EstimateMovedDocsRatio() + : EstimateMovedDocsRatio(false) +{ +} + +EstimateMovedDocsRatio::EstimateMovedDocsRatio(bool verbose) + : _verbose(verbose) +{ +} + +double +EstimateMovedDocsRatio::estimate_lost_docs_base_ratio(uint32_t redundancy, uint32_t lost_nodes, uint32_t num_nodes) +{ + if (redundancy > lost_nodes) { + return 0.0; + } + double loss_ratio = 1.0; + for (uint32_t i = 0; i < redundancy; ++i) { + loss_ratio *= ((double) (lost_nodes - i)) / (num_nodes - i); + } + if (_verbose) { + LOG(info, "estimated lost docs base ratio: %4.2f", loss_ratio); + } + return loss_ratio; +} + +double +EstimateMovedDocsRatio::estimate_moved_docs_ratio_grow(uint32_t redundancy, uint32_t added_nodes, uint32_t num_nodes) +{ + if (added_nodes == num_nodes) { + return 0.0; + } + double new_redundancy = redundancy; + double new_per_node_doc_ratio = new_redundancy / num_nodes; + double moved_ratio = new_per_node_doc_ratio * added_nodes; + if (_verbose) { + LOG(info, "estimated_moved_docs_ratio_grow(%u,%u,%u)=%4.2f", redundancy, added_nodes, num_nodes, moved_ratio); + } + return moved_ratio; +} + +double +EstimateMovedDocsRatio::estimate_moved_docs_ratio_shrink(uint32_t redundancy, uint32_t retired_nodes, uint32_t num_nodes) +{ + if (retired_nodes == num_nodes) { + return 0.0; + } + double old_redundancy = redundancy; + double old_per_node_doc_ratio = old_redundancy / num_nodes; + uint32_t new_nodes = num_nodes - retired_nodes; + double new_redundancy = std::min(redundancy, new_nodes); + double new_per_node_doc_ratio = new_redundancy / new_nodes; + double moved_ratio = (new_per_node_doc_ratio - old_per_node_doc_ratio) * new_nodes; + if (_verbose) { + LOG(info, "estimated_moved_docs_ratio_shrink(%u,%u,%u)=%4.2f", redundancy, retired_nodes, num_nodes, moved_ratio); + } + return moved_ratio; +} + +double +EstimateMovedDocsRatio::estimate_moved_docs_ratio_crash(uint32_t redundancy, uint32_t crashed_nodes, uint32_t num_nodes) +{ + if (crashed_nodes == num_nodes) { + return 0.0; + } + double old_redundancy = redundancy; + double old_per_node_doc_ratio = old_redundancy / num_nodes; + uint32_t new_nodes = num_nodes - crashed_nodes; + double new_redundancy = std::min(redundancy, new_nodes); + double new_per_node_doc_ratio = new_redundancy / new_nodes; + double lost_docs_ratio = estimate_lost_docs_base_ratio(redundancy, crashed_nodes, num_nodes) * new_redundancy; + double moved_ratio = (new_per_node_doc_ratio - old_per_node_doc_ratio) * new_nodes - lost_docs_ratio; + if (_verbose) { + LOG(info, "estimated_moved_docs_ratio_crash(%u,%u,%u)=%4.2f", redundancy, crashed_nodes, num_nodes, moved_ratio); + } + return moved_ratio; +} + +double +EstimateMovedDocsRatio::estimate_moved_docs_ratio_replace(uint32_t redundancy, uint32_t added_nodes, uint32_t retired_nodes, uint32_t num_nodes) +{ + if (added_nodes == num_nodes || retired_nodes == num_nodes) { + return 0.0; + } + uint32_t old_nodes = num_nodes - added_nodes; + double old_redundancy = std::min(redundancy, old_nodes); + [[maybe_unused]] double old_per_node_doc_ratio = old_redundancy / old_nodes; + uint32_t new_nodes = num_nodes - retired_nodes; + double new_redundancy = std::min(redundancy, new_nodes); + double new_per_node_doc_ratio = new_redundancy / new_nodes; + double moved_ratio = new_per_node_doc_ratio * added_nodes; + uint32_t stable_nodes = num_nodes - added_nodes - retired_nodes; + // Account for extra documents moved from retired nodes to stable nodes + // TODO: Fix calculation + double baseline_per_node_doc_ratio = ((double) redundancy) / num_nodes; + double extra_per_stable_node_doc_ratio = std::min(baseline_per_node_doc_ratio * retired_nodes / new_nodes, 1.0 - old_per_node_doc_ratio); + double extra_moved_ratio = extra_per_stable_node_doc_ratio * stable_nodes; + moved_ratio += extra_moved_ratio; + if (_verbose) { + LOG(info, "estimated_moved_docs_ratio_replace(%u,%u,%u,%u)=%4.2f, (of which %4.2f extra)", redundancy, added_nodes, retired_nodes, num_nodes, moved_ratio, extra_moved_ratio); + } + return moved_ratio; +} + +} diff --git a/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.h b/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.h new file mode 100644 index 00000000000..fb14e80098d --- /dev/null +++ b/searchcore/src/vespa/searchcore/bmcluster/estimate_moved_docs_ratio.h @@ -0,0 +1,25 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <cstdint> + +namespace search::bmcluster { + +/* + * Class for estimating moved docs ratio during + * document redistribution. + */ +class EstimateMovedDocsRatio { + bool _verbose; +public: + EstimateMovedDocsRatio(); + explicit EstimateMovedDocsRatio(bool verbose); + double estimate_lost_docs_base_ratio(uint32_t redundancy, uint32_t lost_nodes, uint32_t num_nodes); + double estimate_moved_docs_ratio_grow(uint32_t redundancy, uint32_t added_nodes, uint32_t num_nodes); + double estimate_moved_docs_ratio_shrink(uint32_t redundancy, uint32_t retired_nodes, uint32_t num_nodes); + double estimate_moved_docs_ratio_crash(uint32_t redundancy, uint32_t crashed_nodes, uint32_t num_nodes); + double estimate_moved_docs_ratio_replace(uint32_t redundancy, uint32_t added_nodes, uint32_t retired_nodes, uint32_t num_nodes); +}; + +} diff --git a/searchlib/abi-spec.json b/searchlib/abi-spec.json index e8e3a3cb133..2468fd0c5c7 100644 --- a/searchlib/abi-spec.json +++ b/searchlib/abi-spec.json @@ -1034,6 +1034,7 @@ "public static final int LDEXP", "public static final int POW", "public static final int BIT", + "public static final int HAMMING", "public static final int MAP", "public static final int REDUCE", "public static final int JOIN", @@ -1389,7 +1390,8 @@ "public static final enum com.yahoo.searchlib.rankingexpression.rule.Function max", "public static final enum com.yahoo.searchlib.rankingexpression.rule.Function min", "public static final enum com.yahoo.searchlib.rankingexpression.rule.Function pow", - "public static final enum com.yahoo.searchlib.rankingexpression.rule.Function bit" + "public static final enum com.yahoo.searchlib.rankingexpression.rule.Function bit", + "public static final enum com.yahoo.searchlib.rankingexpression.rule.Function hamming" ] }, "com.yahoo.searchlib.rankingexpression.rule.FunctionNode": { diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java index e41732f9d16..33ba3c6ef4b 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java @@ -157,6 +157,7 @@ public class TensorValue extends Value { case fmod: return value.fmod(argument); case ldexp: return value.ldexp(argument); case bit: return value.bit(argument); + case hamming: return value.hamming(argument); default: throw new UnsupportedOperationException("Cannot combine two tensors using " + function); } } diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Function.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Function.java index 16aa947986d..3958711f74b 100644 --- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Function.java +++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Function.java @@ -46,7 +46,8 @@ public enum Function implements Serializable { max(2) { public double evaluate(double x, double y) { return max(x,y); } }, min(2) { public double evaluate(double x, double y) { return min(x,y); } }, pow(2) { public double evaluate(double x, double y) { return pow(x,y); } }, - bit(2) { public double evaluate(double x, double y) { return ((int)y < 8 && (int)y >= 0 && ((int)x & (1 << (int)y)) != 0) ? 1.0 : 0.0; } }; + bit(2) { public double evaluate(double x, double y) { return ((int)y < 8 && (int)y >= 0 && ((int)x & (1 << (int)y)) != 0) ? 1.0 : 0.0; } }, + hamming(2) { public double evaluate(double x, double y) { return ScalarFunctions.Hamming.hamming(x, y); } }; private final int arity; diff --git a/searchlib/src/main/javacc/RankingExpressionParser.jj b/searchlib/src/main/javacc/RankingExpressionParser.jj index 99eff010628..7bfbfd6c005 100755 --- a/searchlib/src/main/javacc/RankingExpressionParser.jj +++ b/searchlib/src/main/javacc/RankingExpressionParser.jj @@ -124,6 +124,7 @@ TOKEN : // MIN <POW: "pow"> | <BIT: "bit"> | + <HAMMING: "hamming"> | <MAP: "map"> | <REDUCE: "reduce"> | @@ -735,7 +736,8 @@ Function binaryFunctionName() : { } <MAX> { return Function.max; } | <MIN> { return Function.min; } | <POW> { return Function.pow; } | - <BIT> { return Function.bit; } + <BIT> { return Function.bit; } | + <HAMMING> { return Function.hamming; } } List<ExpressionNode> expressionList() : diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java index 4a3c4b248be..246dbcb2b1e 100644 --- a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java +++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java @@ -288,6 +288,8 @@ public class EvaluationTestCase { tester.assertEvaluates("{ {h:0}:1.5, {h:1}:1.5 }", "0.5 + tensor0", "{ {h:0}:1.0,{h:1}:1.0 }"); tester.assertEvaluates("{ {x:0,y:0}:0, {x:1,y:0}:0 }", "atan2(tensor0, tensor1)", "{ {x:0}:0, {x:1}:0 }", "{ {y:0}:1 }"); + tester.assertEvaluates("{ {x:0,y:0}:2, {x:1,y:0}:7 }", + "hamming(tensor0, tensor1)", "{ {x:0}:97, {x:1}:-1 }", "{ {y:0}:1 }"); tester.assertEvaluates("{ {x:0,y:0}:0, {x:1,y:0}:1 }", "tensor0 > tensor1", "{ {x:0}:3, {x:1}:7 }", "{ {y:0}:5 }"); tester.assertEvaluates("{ {x:0,y:0}:1, {x:1,y:0}:0 }", diff --git a/searchlib/src/tests/rankingexpression/rankingexpressionlist b/searchlib/src/tests/rankingexpression/rankingexpressionlist index 77b2294c668..d41570732d9 100644 --- a/searchlib/src/tests/rankingexpression/rankingexpressionlist +++ b/searchlib/src/tests/rankingexpression/rankingexpressionlist @@ -87,6 +87,7 @@ floor(10) relu(10) sigmoid(10) atan2(10, 20); atan2(10,20) +hamming(42, -16); hamming(42,-16) ldexp(10, 20); ldexp(10,20) pow(10, 20); pow(10,20) fmod(10, 20); fmod(10,20) diff --git a/searchlib/src/vespa/searchlib/engine/search_protocol_proto.h b/searchlib/src/vespa/searchlib/engine/search_protocol_proto.h index e2eeca74e44..bb9887f6e74 100644 --- a/searchlib/src/vespa/searchlib/engine/search_protocol_proto.h +++ b/searchlib/src/vespa/searchlib/engine/search_protocol_proto.h @@ -7,6 +7,6 @@ #pragma GCC diagnostic ignored "-Wsuggest-override" #endif -#include "search_protocol.pb.h" +#include <vespa/searchlib/engine/search_protocol.pb.h> #pragma GCC diagnostic pop diff --git a/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp b/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp index 0a13b196c43..90369009f0e 100644 --- a/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp +++ b/searchlib/src/vespa/searchlib/fef/blueprintresolver.cpp @@ -3,8 +3,10 @@ #include "blueprintresolver.h" #include "blueprintfactory.h" #include "featurenameparser.h" +#include <vespa/vespalib/util/lambdatask.h> #include <vespa/vespalib/util/stringfmt.h> #include <vespa/vespalib/util/size_literals.h> +#include <vespa/vespalib/util/threadstackexecutor.h> #include <stack> #include <cassert> #include <set> @@ -14,6 +16,8 @@ LOG_SETUP(".fef.blueprintresolver"); using vespalib::make_string_short::fmt; +using vespalib::ThreadStackExecutor; +using vespalib::makeLambdaTask; namespace search::fef { @@ -271,8 +275,7 @@ BlueprintResolver::compile() { assert(_executorSpecs.empty()); // only one compilation allowed Compiler compiler(_factory, _indexEnv, _executorSpecs, _featureMap); - std::thread compile_thread([&]() - { + auto compile_task = makeLambdaTask([&]() { compiler.probe_stack(); for (const auto &seed: _seeds) { auto ref = compiler.resolve_feature(seed, Blueprint::AcceptInput::ANY); @@ -282,7 +285,10 @@ BlueprintResolver::compile() _seedMap.emplace(FeatureNameParser(seed).featureName(), ref); } }); - compile_thread.join(); + ThreadStackExecutor executor(1, 8_Mi); + executor.execute(std::move(compile_task)); + executor.sync(); + executor.shutdown(); size_t stack_usage = compiler.stack_usage(); if (stack_usage > (128_Ki)) { LOG(warning, "high stack usage: %zu bytes", stack_usage); diff --git a/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java b/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java index 215dc311af3..dbabf1274af 100644 --- a/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java +++ b/security-utils/src/main/java/com/yahoo/security/X509CertificateUtils.java @@ -21,10 +21,13 @@ import java.io.UncheckedIOException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Duration; @@ -178,4 +181,16 @@ public class X509CertificateUtils { .build(); return new X509CertificateWithKey(cert, keyPair.getPrivate()); } + + /** + * @return certificate SHA-1 fingerprint + */ + public static byte[] getX509CertificateFingerPrint(X509Certificate certificate) { + try { + MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + return sha1.digest(certificate.getEncoded()); + } catch (CertificateEncodingException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } } diff --git a/slobrok/src/apps/slobrok/slobrok.cpp b/slobrok/src/apps/slobrok/slobrok.cpp index b2748762a12..18a401e1db6 100644 --- a/slobrok/src/apps/slobrok/slobrok.cpp +++ b/slobrok/src/apps/slobrok/slobrok.cpp @@ -50,7 +50,6 @@ App::Main() { uint32_t portnum = 2773; vespalib::string cfgId; - bool useNewLogic = false; int argi = 1; const char* optArg; @@ -64,7 +63,7 @@ App::Main() portnum = atoi(optArg); break; case 'N': - useNewLogic = true; + // ignored break; default: LOG(error, "unknown option letter '%c'", c); @@ -76,11 +75,11 @@ App::Main() if (cfgId.empty()) { LOG(debug, "no config id specified"); ConfigShim shim(portnum); - mainobj = std::make_unique<SBEnv>(shim, useNewLogic); + mainobj = std::make_unique<SBEnv>(shim); } else { ConfigShim shim(portnum, cfgId); shim.enableStateServer(true); - mainobj = std::make_unique<SBEnv>(shim, useNewLogic); + mainobj = std::make_unique<SBEnv>(shim); } hook_sigterm(); res = mainobj->MainLoop(); diff --git a/slobrok/src/tests/rpc_mapping_monitor/rpc_mapping_monitor_test.cpp b/slobrok/src/tests/rpc_mapping_monitor/rpc_mapping_monitor_test.cpp index f503453f934..0edcde172aa 100644 --- a/slobrok/src/tests/rpc_mapping_monitor/rpc_mapping_monitor_test.cpp +++ b/slobrok/src/tests/rpc_mapping_monitor/rpc_mapping_monitor_test.cpp @@ -148,7 +148,7 @@ TEST_F(RpcMappingMonitorTest, hurry_means_faster) { EXPECT_TRUE(debugger.step_until([&]() { return ((hist.map[foo_a].samples() > 0)); })); auto t2 = debugger.time(); - fprintf(stderr, "hurry: ~%zu ms, normal: ~%zu ms\n", count_ms(t1-t0), count_ms(t2-t0)); + fprintf(stderr, "hurry: ~%" PRIu64 " ms, normal: ~%" PRIu64 " ms\n", count_ms(t1-t0), count_ms(t2-t0)); EXPECT_GT((t2 - t0), 10 * (t1 - t0)); EXPECT_EQ(hist.map[foo_a].state(), State::UP); EXPECT_EQ(hist.map[baz_b].state(), State::UP); @@ -218,7 +218,7 @@ TEST_F(RpcMappingMonitorTest, detect_ping_interval) { a.set_last_conn(nullptr); EXPECT_TRUE(debugger.step_until([&]() { return (a.last_conn); })); auto t2 = debugger.time(); - fprintf(stderr, "ping interval: ~%zu ms\n", count_ms(t2-t1)); + fprintf(stderr, "ping interval: ~%" PRIu64 " ms\n", count_ms(t2-t1)); } GTEST_MAIN_RUN_ALL_TESTS() diff --git a/slobrok/src/vespa/slobrok/server/CMakeLists.txt b/slobrok/src/vespa/slobrok/server/CMakeLists.txt index ae1a05c5181..5168758e46d 100644 --- a/slobrok/src/vespa/slobrok/server/CMakeLists.txt +++ b/slobrok/src/vespa/slobrok/server/CMakeLists.txt @@ -1,7 +1,6 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. vespa_add_library(slobrok_slobrokserver SOURCES - cmd.cpp configshim.cpp exchange_manager.cpp i_monitored_server.cpp @@ -23,8 +22,6 @@ vespa_add_library(slobrok_slobrokserver request_completion_handler.cpp reserved_name.cpp rpc_mapping_monitor.cpp - rpc_server_manager.cpp - rpc_server_map.cpp rpchooks.cpp rpcmirror.cpp sbenv.cpp diff --git a/slobrok/src/vespa/slobrok/server/cmd.cpp b/slobrok/src/vespa/slobrok/server/cmd.cpp deleted file mode 100644 index df856189d89..00000000000 --- a/slobrok/src/vespa/slobrok/server/cmd.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - - -#include "cmd.h" -#include "rpc_server_map.h" -#include "reserved_name.h" -#include "remote_slobrok.h" -#include "sbenv.h" - -#include <vespa/log/log.h> -LOG_SETUP(".slobrok.server.cmd"); - -namespace slobrok { - -//----------------------------------------------------------------------------- - -struct ScriptData { - SBEnv &env; - const std::string name; - const std::string spec; - FRT_RPCRequest * const registerRequest; - - enum { - RDC_INIT, XCH_WANTADD, CHK_RPCSRV, XCH_DOADD, XCH_IGNORE, RDC_INVAL - } _state; - - ScriptData(SBEnv &e, const std::string &n, const std::string &s, FRT_RPCRequest *r) - : env(e), name(n), spec(s), registerRequest(r), _state(RDC_INIT) - {} -}; - -//----------------------------------------------------------------------------- - -const std::string & -ScriptCommand::name() { return _data->name; } - -const std::string & -ScriptCommand::spec() { return _data->spec; } - -ScriptCommand::ScriptCommand(std::unique_ptr<ScriptData> data) - : _data(std::move(data)) -{} - -ScriptCommand::ScriptCommand(ScriptCommand &&) = default; -ScriptCommand& -ScriptCommand::operator= (ScriptCommand &&) = default; -ScriptCommand::~ScriptCommand() = default; - -ScriptCommand -ScriptCommand::makeRegRpcSrvCmd(SBEnv &env, - const std::string &name, const std::string &spec, - FRT_RPCRequest *req) -{ - return ScriptCommand(std::make_unique<ScriptData>(env, name, spec, req)); -} - -ScriptCommand -ScriptCommand::makeIgnoreCmd(SBEnv &env, const std::string & name, const std::string &spec) -{ - auto data = std::make_unique<ScriptData>(env, name, spec, nullptr); - data->_state = ScriptData::XCH_IGNORE; - return ScriptCommand(std::move(data)); -} - -ScriptCommand -ScriptCommand::makeRegCompleter(SBEnv &env, - const std::string &name, const std::string &spec, - FRT_RPCRequest *req) -{ - auto data = std::make_unique<ScriptData>(env, name, spec, req); - data->_state = ScriptData::XCH_DOADD; - return ScriptCommand(std::move(data)); -} - -void -ScriptCommand::doRequest() -{ - LOG_ASSERT(_data->_state == ScriptData::RDC_INIT); - doneHandler(OkState()); -} - -void cleanupReservation(ScriptData & data) -{ - RpcServerMap &map = data.env.rpcServerMap(); - const ReservedName *rsvp = map.getReservation(data.name.c_str()); - if (rsvp != nullptr && rsvp->isLocal) { - map.removeReservation(data.name.c_str()); - } -} - -void -ScriptCommand::doneHandler(OkState result) -{ - LOG_ASSERT(_data); - std::unique_ptr<ScriptData> dataUP = std::move(_data); - LOG_ASSERT(! _data); - ScriptData & data = *dataUP; - const char *name_p = data.name.c_str(); - const char *spec_p = data.spec.c_str(); - ExchangeManager &xch = data.env.exchangeManager(); - RpcServerManager &rsm = data.env.rpcServerManager(); - - if (result.failed()) { - LOG(warning, "failed [%s->%s] in state %d: %s", name_p, spec_p, data._state, result.errorMsg.c_str()); - if (data._state != ScriptData::XCH_IGNORE) { - cleanupReservation(data); - } - // XXX should handle different state errors differently? - if (data.registerRequest != nullptr) { - data.registerRequest->SetError(FRTE_RPC_METHOD_FAILED, result.errorMsg.c_str()); - data.registerRequest->Return(); - } else { - LOG(warning, "ignored: %s", result.errorMsg.c_str()); - } - return; - } - if (data._state == ScriptData::RDC_INIT) { - LOG(spam, "phase wantAdd(%s,%s)", name_p, spec_p); - data._state = ScriptData::XCH_WANTADD; - xch.wantAdd(std::move(dataUP)); - return; - } else if (data._state == ScriptData::XCH_WANTADD) { - LOG(spam, "phase addManaged(%s,%s)", name_p, spec_p); - data._state = ScriptData::CHK_RPCSRV; - rsm.addManaged(std::move(dataUP)); - return; - } else if (data._state == ScriptData::CHK_RPCSRV) { - LOG(spam, "phase doAdd(%s,%s)", name_p, spec_p); - data._state = ScriptData::XCH_DOADD; - xch.doAdd(std::move(dataUP)); - return; - } else if (data._state == ScriptData::XCH_DOADD) { - LOG(debug, "done doAdd(%s,%s)", name_p, spec_p); - data._state = ScriptData::RDC_INVAL; - // all OK - if (data.registerRequest != nullptr) { - data.registerRequest->Return(); - } - cleanupReservation(data); - return; - } else if (data._state == ScriptData::XCH_IGNORE) { - return; - } - // no other state should be possible - LOG_ABORT("should not be reached"); -} - -//----------------------------------------------------------------------------- - -} // namespace slobrok diff --git a/slobrok/src/vespa/slobrok/server/cmd.h b/slobrok/src/vespa/slobrok/server/cmd.h deleted file mode 100644 index e7f42f75e42..00000000000 --- a/slobrok/src/vespa/slobrok/server/cmd.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#pragma once - -#include "ok_state.h" -#include <memory> - -class FRT_RPCRequest; - -namespace slobrok { - -class SBEnv; -struct ScriptData; - -class ScriptCommand -{ -private: - std::unique_ptr<ScriptData> _data; - ScriptCommand(std::unique_ptr<ScriptData> data); -public: - const std::string &name(); - const std::string &spec(); - - ScriptCommand(ScriptCommand &&); - ScriptCommand& operator= (ScriptCommand &&); - ~ScriptCommand(); - - static ScriptCommand makeRegRpcSrvCmd(SBEnv &env, const std::string &name, const std::string &spec, FRT_RPCRequest *req); - static ScriptCommand makeIgnoreCmd(SBEnv &env, const std::string &name, const std::string &spec); - static ScriptCommand makeRegCompleter(SBEnv &env, const std::string &name, const std::string &spec, FRT_RPCRequest *req); - - void doneHandler(OkState result); - void doRequest(); -}; - -} // namespace slobrok diff --git a/slobrok/src/vespa/slobrok/server/exchange_manager.cpp b/slobrok/src/vespa/slobrok/server/exchange_manager.cpp index 94e951ca252..89167842c1b 100644 --- a/slobrok/src/vespa/slobrok/server/exchange_manager.cpp +++ b/slobrok/src/vespa/slobrok/server/exchange_manager.cpp @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "exchange_manager.h" -#include "rpc_server_map.h" #include "sbenv.h" #include <vespa/fnet/frt/supervisor.h> #include <vespa/vespalib/util/overload.h> @@ -14,11 +13,9 @@ namespace slobrok { //----------------------------------------------------------------------------- -ExchangeManager::ExchangeManager(SBEnv &env, RpcServerMap &rpcsrvmap) +ExchangeManager::ExchangeManager(SBEnv &env) : _partners(), - _env(env), - _rpcsrvmanager(env.rpcServerManager()), - _rpcsrvmap(rpcsrvmap) + _env(env) { } @@ -69,35 +66,13 @@ ExchangeManager::getPartnerList() void ExchangeManager::forwardRemove(const std::string & name, const std::string & spec) { - WorkPackage *package = new WorkPackage(WorkPackage::OP_REMOVE, *this, - ScriptCommand::makeIgnoreCmd(_env, name, spec)); + WorkPackage *package = new WorkPackage(WorkPackage::OP_REMOVE, ServiceMapping{name, spec}, *this); for (const auto & entry : _partners) { package->addItem(entry.second.get()); } package->expedite(); } -void -ExchangeManager::doAdd(ScriptCommand rdc) -{ - WorkPackage *package = new WorkPackage(WorkPackage::OP_DOADD, *this, std::move(rdc)); - - for (const auto & entry : _partners) { - package->addItem(entry.second.get()); - } - package->expedite(); -} - - -void -ExchangeManager::wantAdd(ScriptCommand rdc) -{ - WorkPackage *package = new WorkPackage(WorkPackage::OP_WANTADD, *this, std::move(rdc)); - for (const auto & entry : _partners) { - package->addItem(entry.second.get()); - } - package->expedite(); -} RemoteSlobrok * ExchangeManager::lookupPartner(const std::string & name) const { @@ -128,22 +103,8 @@ void ExchangeManager::healthCheck() { auto newWorldList = env().consensusMap().currentConsensus(); - if (! _env.useNewLogic()) { - auto oldWorldServices = env().rpcServerMap().allManaged(); - ServiceMappingList oldWorldList; - for (const auto *nsp : oldWorldServices) { - oldWorldList.emplace_back(nsp->getName(), nsp->getSpec()); - } - std::sort(oldWorldList.begin(), oldWorldList.end()); - vespalib::string diff = diffLists(oldWorldList, newWorldList); - if (! diff.empty()) { - LOG(warning, "Diff from old world rpcServerMap to new world consensus map: %s", - diff.c_str()); - } - } for (const auto & [ name, partner ] : _partners) { partner->maybeStartFetch(); - partner->maybePushMine(); auto remoteList = partner->remoteMap().allMappings(); // 0 is expected (when remote is down) if (remoteList.size() != 0) { @@ -205,14 +166,12 @@ ExchangeManager::WorkPackage::WorkItem::~WorkItem() } -ExchangeManager::WorkPackage::WorkPackage(op_type op, - ExchangeManager &exchanger, - ScriptCommand script) +ExchangeManager::WorkPackage::WorkPackage(op_type op, const ServiceMapping &mapping, ExchangeManager &exchanger) : _work(), _doneCnt(0), _numDenied(0), - _script(std::move(script)), _exchanger(exchanger), + _mapping(mapping), _optype(op) { } @@ -230,9 +189,9 @@ ExchangeManager::WorkPackage::doneItem(bool denied) (int)_doneCnt, (int)_work.size(), (int)_numDenied); if (_doneCnt == _work.size()) { if (_numDenied > 0) { - _script.doneHandler(OkState(_numDenied, "denied by remote")); - } else { - _script.doneHandler(OkState()); + LOG(debug, "work package [%s->%s]: %zd/%zd denied by remote", + _mapping.name.c_str(), _mapping.spec.c_str(), + _numDenied, _doneCnt); } delete this; } @@ -245,18 +204,12 @@ ExchangeManager::WorkPackage::addItem(RemoteSlobrok *partner) if (! partner->isConnected()) { return; } - const char *name_p = _script.name().c_str(); - const char *spec_p = _script.spec().c_str(); + const char *name_p = _mapping.name.c_str(); + const char *spec_p = _mapping.spec.c_str(); FRT_RPCRequest *r = _exchanger._env.getSupervisor()->AllocRPCRequest(); - // XXX should recheck rpcsrvmap again - if (_optype == OP_REMOVE) { - r->SetMethodName("slobrok.internal.doRemove"); - } else if (_optype == OP_WANTADD) { - r->SetMethodName("slobrok.internal.wantAdd"); - } else if (_optype == OP_DOADD) { - r->SetMethodName("slobrok.internal.doAdd"); - } + LOG_ASSERT(_optype == OP_REMOVE); + r->SetMethodName("slobrok.internal.doRemove"); r->GetParams()->AddString(_exchanger._env.mySpec().c_str()); r->GetParams()->AddString(name_p); r->GetParams()->AddString(spec_p); @@ -274,7 +227,6 @@ ExchangeManager::WorkPackage::expedite() size_t sz = _work.size(); if (sz == 0) { // no remotes need doing. - _script.doneHandler(OkState()); delete this; return; } diff --git a/slobrok/src/vespa/slobrok/server/exchange_manager.h b/slobrok/src/vespa/slobrok/server/exchange_manager.h index 18a20274a75..6891686c94d 100644 --- a/slobrok/src/vespa/slobrok/server/exchange_manager.h +++ b/slobrok/src/vespa/slobrok/server/exchange_manager.h @@ -2,7 +2,6 @@ #pragma once #include "ok_state.h" -#include "cmd.h" #include "remote_slobrok.h" #include <deque> @@ -14,8 +13,6 @@ namespace slobrok { //----------------------------------------------------------------------------- class SBEnv; -class RpcServerMap; -class RpcServerManager; //----------------------------------------------------------------------------- @@ -61,37 +58,31 @@ private: std::vector<std::unique_ptr<WorkItem>> _work; size_t _doneCnt; size_t _numDenied; - ScriptCommand _script; public: ExchangeManager &_exchanger; - enum op_type { OP_NOP, OP_WANTADD, OP_DOADD, OP_REMOVE }; - op_type _optype; + enum op_type { OP_REMOVE }; + const ServiceMapping _mapping; + const op_type _optype; void addItem(RemoteSlobrok *partner); void doneItem(bool denied); void expedite(); WorkPackage(const WorkPackage&) = delete; WorkPackage& operator= (const WorkPackage&) = delete; - WorkPackage(op_type op, - ExchangeManager &exchanger, - ScriptCommand donehandler); + WorkPackage(op_type op, const ServiceMapping &mapping, ExchangeManager &exchanger); ~WorkPackage(); }; SBEnv &_env; - RpcServerManager &_rpcsrvmanager; - RpcServerMap &_rpcsrvmap; vespalib::string diffLists(const ServiceMappingList &lhs, const ServiceMappingList &rhs); public: ExchangeManager(const ExchangeManager &) = delete; ExchangeManager &operator=(const ExchangeManager &) = delete; - ExchangeManager(SBEnv &env, RpcServerMap &rpcsrvmap); + ExchangeManager(SBEnv &env); ~ExchangeManager(); SBEnv &env() { return _env; } - RpcServerManager &rpcServerManager() { return _rpcsrvmanager; } - RpcServerMap &rpcServerMap() { return _rpcsrvmap; } OkState addPartner(const std::string & spec); void removePartner(const std::string & spec); @@ -99,9 +90,6 @@ public: void forwardRemove(const std::string & name, const std::string & spec); - void wantAdd(ScriptCommand rdc); - void doAdd(ScriptCommand rdc); - RemoteSlobrok *lookupPartner(const std::string & name) const; void healthCheck(); }; diff --git a/slobrok/src/vespa/slobrok/server/local_rpc_monitor_map.h b/slobrok/src/vespa/slobrok/server/local_rpc_monitor_map.h index 96c0ee03245..173a0455e43 100644 --- a/slobrok/src/vespa/slobrok/server/local_rpc_monitor_map.h +++ b/slobrok/src/vespa/slobrok/server/local_rpc_monitor_map.h @@ -1,8 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #pragma once -#include "cmd.h" -#include "managed_rpc_server.h" #include "map_listener.h" #include "map_source.h" #include "mapping_monitor.h" @@ -12,6 +10,8 @@ #include "service_map_history.h" #include "service_mapping.h" +#include <vespa/fnet/task.h> + #include <vector> #include <memory> #include <map> diff --git a/slobrok/src/vespa/slobrok/server/remote_check.cpp b/slobrok/src/vespa/slobrok/server/remote_check.cpp index 157b959dbfe..da4d1ebc3dd 100644 --- a/slobrok/src/vespa/slobrok/server/remote_check.cpp +++ b/slobrok/src/vespa/slobrok/server/remote_check.cpp @@ -2,8 +2,6 @@ #include "remote_check.h" #include "named_service.h" -#include "rpc_server_map.h" -#include "rpc_server_manager.h" #include "remote_slobrok.h" #include "random.h" #include "exchange_manager.h" @@ -13,12 +11,9 @@ LOG_SETUP(".slobrok.server.remote_check"); namespace slobrok { -RemoteCheck::RemoteCheck(FNET_Scheduler *sched, - RpcServerMap& rpcsrvmap, - RpcServerManager& rpcsrvman, - ExchangeManager& exch) +RemoteCheck::RemoteCheck(FNET_Scheduler *sched, ExchangeManager& exch) : FNET_Task(sched), - _rpcsrvmap(rpcsrvmap), _rpcsrvmanager(rpcsrvman), _exchanger(exch) + _exchanger(exch) { double seconds = randomIn(15.3, 27.9); Schedule(seconds); diff --git a/slobrok/src/vespa/slobrok/server/remote_check.h b/slobrok/src/vespa/slobrok/server/remote_check.h index e0cf89c177d..11eb85401fe 100644 --- a/slobrok/src/vespa/slobrok/server/remote_check.h +++ b/slobrok/src/vespa/slobrok/server/remote_check.h @@ -20,17 +20,12 @@ class ExchangeManager; class RemoteCheck : public FNET_Task { private: - RpcServerMap &_rpcsrvmap; - RpcServerManager &_rpcsrvmanager; ExchangeManager &_exchanger; RemoteCheck(const RemoteCheck &); // Not used RemoteCheck &operator=(const RemoteCheck &); // Not used public: - explicit RemoteCheck(FNET_Scheduler *sched, - RpcServerMap& rpcsrvmap, - RpcServerManager& rpcsrvman, - ExchangeManager& exchanger); + explicit RemoteCheck(FNET_Scheduler *sched, ExchangeManager& exchanger); ~RemoteCheck(); private: void PerformTask() override; diff --git a/slobrok/src/vespa/slobrok/server/remote_slobrok.cpp b/slobrok/src/vespa/slobrok/server/remote_slobrok.cpp index d867d955dac..4b308e90e37 100644 --- a/slobrok/src/vespa/slobrok/server/remote_slobrok.cpp +++ b/slobrok/src/vespa/slobrok/server/remote_slobrok.cpp @@ -1,8 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "remote_slobrok.h" -#include "rpc_server_map.h" -#include "rpc_server_manager.h" #include "exchange_manager.h" #include "sbenv.h" #include <vespa/fnet/frt/supervisor.h> @@ -18,7 +16,6 @@ namespace slobrok { RemoteSlobrok::RemoteSlobrok(const std::string &name, const std::string &spec, ExchangeManager &manager) : _exchanger(manager), - _rpcsrvmanager(manager.rpcServerManager()), _remote(nullptr), _serviceMapMirror(), _rpcserver(name, spec, *this), @@ -26,11 +23,7 @@ RemoteSlobrok::RemoteSlobrok(const std::string &name, const std::string &spec, _failCnt(0), _consensusSubscription(MapSubscription::subscribe(_serviceMapMirror, _exchanger.env().consensusMap())), _remAddPeerReq(nullptr), - _remListReq(nullptr), - _remAddReq(nullptr), - _remRemReq(nullptr), - _remFetchReq(nullptr), - _pending() + _remFetchReq(nullptr) { _rpcserver.healthCheck(); } @@ -38,8 +31,6 @@ RemoteSlobrok::RemoteSlobrok(const std::string &name, const std::string &spec, void RemoteSlobrok::shutdown() { _reconnecter.disable(); - _pending.clear(); - if (_remote != nullptr) { _remote->SubRef(); _remote = nullptr; @@ -51,15 +42,6 @@ void RemoteSlobrok::shutdown() { if (_remAddPeerReq != nullptr) { _remAddPeerReq->Abort(); } - if (_remListReq != nullptr) { - _remListReq->Abort(); - } - if (_remAddReq != nullptr) { - _remAddReq->Abort(); - } - if (_remRemReq != nullptr) { - _remRemReq->Abort(); - } _serviceMapMirror.clear(); } @@ -68,39 +50,6 @@ RemoteSlobrok::~RemoteSlobrok() { // _rpcserver destructor called automatically } -void -RemoteSlobrok::doPending() -{ - if (_remAddReq != nullptr) return; - if (_remRemReq != nullptr) return; - - if (_remote == nullptr) return; - - if ( ! _pending.empty() ) { - std::unique_ptr<NamedService> todo = std::move(_pending.front()); - _pending.pop_front(); - - const NamedService *rpcsrv = _exchanger.rpcServerMap().lookup(todo->getName()); - - if (rpcsrv == nullptr) { - _remRemReq = getSupervisor()->AllocRPCRequest(); - _remRemReq->SetMethodName("slobrok.internal.doRemove"); - _remRemReq->GetParams()->AddString(_exchanger.env().mySpec().c_str()); - _remRemReq->GetParams()->AddString(todo->getName().c_str()); - _remRemReq->GetParams()->AddString(todo->getSpec().c_str()); - _remote->InvokeAsync(_remRemReq, 2.0, this); - } else { - _remAddReq = getSupervisor()->AllocRPCRequest(); - _remAddReq->SetMethodName("slobrok.internal.doAdd"); - _remAddReq->GetParams()->AddString(_exchanger.env().mySpec().c_str()); - _remAddReq->GetParams()->AddString(todo->getName().c_str()); - _remAddReq->GetParams()->AddString(rpcsrv->getSpec().c_str()); - _remote->InvokeAsync(_remAddReq, 2.0, this); - } - // XXX should save this and pick up on RequestDone() - } -} - void RemoteSlobrok::maybeStartFetch() { if (_remFetchReq != nullptr) return; if (_remote == nullptr) return; @@ -168,21 +117,6 @@ void RemoteSlobrok::handleFetchResult() { } } - - -void -RemoteSlobrok::pushMine() -{ - // all mine - std::vector<const NamedService *> mine = _exchanger.rpcServerMap().allManaged(); - while (mine.size() > 0) { - const NamedService *now = mine.back(); - mine.pop_back(); - _pending.push_back(std::make_unique<NamedService>(now->getName(), now->getSpec())); - } - doPending(); -} - void RemoteSlobrok::RequestDone(FRT_RPCRequest *req) { @@ -190,7 +124,6 @@ RemoteSlobrok::RequestDone(FRT_RPCRequest *req) handleFetchResult(); return; } - FRT_Values &answer = *(req->GetReturn()); if (req == _remAddPeerReq) { // handle response after asking remote slobrok to add me as a peer: if (req->IsError()) { @@ -201,96 +134,15 @@ RemoteSlobrok::RequestDone(FRT_RPCRequest *req) myname, myspec, getName().c_str(), getSpec().c_str(), req->GetErrorMessage()); req->SubRef(); _remAddPeerReq = nullptr; - goto retrylater; + fail(); + return; } req->SubRef(); _remAddPeerReq = nullptr; - // next step is to ask the remote to send its list of managed names: - LOG_ASSERT(_remListReq == nullptr); - _remListReq = getSupervisor()->AllocRPCRequest(); - _remListReq->SetMethodName("slobrok.internal.listManagedRpcServers"); - if (_remote != nullptr) { - _remote->InvokeAsync(_remListReq, 3.0, this); - } - // when _remListReq is returned, our managed list is added - } else if (req == _remListReq) { - // handle the list sent from the remote: - if (req->IsError() - || strcmp(answer.GetTypeString(), "SS") != 0) - { - LOG(error, "error listing remote slobrok %s at %s: %s", - getName().c_str(), getSpec().c_str(), req->GetErrorMessage()); - req->SubRef(); - _remListReq = nullptr; - goto retrylater; - } - uint32_t numNames = answer.GetValue(0)._string_array._len; - uint32_t numSpecs = answer.GetValue(1)._string_array._len; - - if (numNames != numSpecs) { - LOG(error, "inconsistent array lengths from %s at %s", getName().c_str(), getSpec().c_str()); - req->SubRef(); - _remListReq = nullptr; - goto retrylater; - } - FRT_StringValue *names = answer.GetValue(0)._string_array._pt; - FRT_StringValue *specs = answer.GetValue(1)._string_array._pt; - - for (uint32_t idx = 0; idx < numNames; idx++) { - _rpcsrvmanager.addRemote(names[idx]._str, specs[idx]._str); - } - req->SubRef(); - _remListReq = nullptr; - - // next step is to push the ones I own: - maybeStartFetch(); - maybePushMine(); - } else if (req == _remAddReq) { - // handle response after pushing some name that we managed: - if (req->IsError() && (req->GetErrorCode() == FRTE_RPC_CONNECTION || - req->GetErrorCode() == FRTE_RPC_TIMEOUT)) - { - LOG(error, "connection error adding to remote slobrok: %s", req->GetErrorMessage()); - req->SubRef(); - _remAddReq = nullptr; - goto retrylater; - } - if (req->IsError()) { - FRT_Values &args = *req->GetParams(); - const char *rpcsrvname = args[1]._string._str; - const char *rpcsrvspec = args[2]._string._str; - LOG(warning, "error adding [%s -> %s] to remote slobrok: %s", - rpcsrvname, rpcsrvspec, req->GetErrorMessage()); - _rpcsrvmanager.removeLocal(rpcsrvname, rpcsrvspec); - } - req->SubRef(); - _remAddReq = nullptr; - doPending(); - } else if (req == _remRemReq) { - // handle response after pushing some remove we had pending: - if (req->IsError() && (req->GetErrorCode() == FRTE_RPC_CONNECTION || - req->GetErrorCode() == FRTE_RPC_TIMEOUT)) - { - LOG(error, "connection error adding to remote slobrok: %s", req->GetErrorMessage()); - req->SubRef(); - _remRemReq = nullptr; - goto retrylater; - } - if (req->IsError()) { - LOG(warning, "error removing on remote slobrok: %s", req->GetErrorMessage()); - } - req->SubRef(); - _remRemReq = nullptr; - doPending(); } else { LOG(error, "got unknown request back in RequestDone()"); LOG_ASSERT(req == nullptr); } - - return; - retrylater: - fail(); - return; } @@ -321,25 +173,6 @@ RemoteSlobrok::fail() _reconnecter.scheduleTryConnect(); } - -void -RemoteSlobrok::maybePushMine() -{ - if (_remote != nullptr && - _remAddPeerReq == nullptr && - _remListReq == nullptr && - _remAddReq == nullptr && - _remRemReq == nullptr) - { - LOG(debug, "spamming remote at %s with my names", getName().c_str()); - pushMine(); - } else { - LOG(debug, "not pushing mine, as we have: remote %p r.a.p.r=%p r.l.r=%p r.a.r=%p r.r.r=%p", - _remote, _remAddPeerReq, _remListReq, _remAddReq, _remRemReq); - } -} - - void RemoteSlobrok::notifyOkRpcSrv(ManagedRpcServer *rpcsrv) { @@ -357,10 +190,7 @@ RemoteSlobrok::notifyOkRpcSrv(ManagedRpcServer *rpcsrv) _remote = getSupervisor()->GetTarget(getSpec().c_str()); maybeStartFetch(); - // at this point, we will do (in sequence): - // ask peer to connect to us too; - // ask peer for its list of managed rpcservers, adding to our database - // add our managed rpcserver on peer + // at this point, we will ask peer to connect to us too; // any failure will cause disconnect and retry. _remAddPeerReq = getSupervisor()->AllocRPCRequest(); @@ -368,7 +198,6 @@ RemoteSlobrok::notifyOkRpcSrv(ManagedRpcServer *rpcsrv) _remAddPeerReq->GetParams()->AddString(_exchanger.env().mySpec().c_str()); _remAddPeerReq->GetParams()->AddString(_exchanger.env().mySpec().c_str()); _remote->InvokeAsync(_remAddPeerReq, 3.0, this); - // when _remAddPeerReq is returned, our managed list is added via doAdd() } void diff --git a/slobrok/src/vespa/slobrok/server/remote_slobrok.h b/slobrok/src/vespa/slobrok/server/remote_slobrok.h index b980aa90de0..ef7f39c08ed 100644 --- a/slobrok/src/vespa/slobrok/server/remote_slobrok.h +++ b/slobrok/src/vespa/slobrok/server/remote_slobrok.h @@ -2,7 +2,6 @@ #pragma once #include "ok_state.h" -#include "cmd.h" #include "i_rpc_server_manager.h" #include "managed_rpc_server.h" #include "service_map_mirror.h" @@ -12,7 +11,6 @@ namespace slobrok { //----------------------------------------------------------------------------- -class RpcServerManager; class ExchangeManager; //----------------------------------------------------------------------------- @@ -43,7 +41,6 @@ private: }; ExchangeManager &_exchanger; - RpcServerManager &_rpcsrvmanager; FRT_Target *_remote; ServiceMapMirror _serviceMapMirror; ManagedRpcServer _rpcserver; @@ -53,14 +50,8 @@ private: std::unique_ptr<MapSubscription> _consensusSubscription; FRT_RPCRequest *_remAddPeerReq; - FRT_RPCRequest *_remListReq; - FRT_RPCRequest *_remAddReq; - FRT_RPCRequest *_remRemReq; FRT_RPCRequest *_remFetchReq; - std::deque<std::unique_ptr<NamedService>> _pending; - void pushMine(); - void doPending(); void handleFetchResult(); public: @@ -72,7 +63,6 @@ public: void fail(); bool isConnected() const { return (_remote != nullptr); } void tryConnect(); - void maybePushMine(); void maybeStartFetch(); void invokeAsync(FRT_RPCRequest *req, double timeout, FRT_IRequestWait *rwaiter); const std::string & getName() const { return _rpcserver.getName(); } diff --git a/slobrok/src/vespa/slobrok/server/rpc_server_manager.cpp b/slobrok/src/vespa/slobrok/server/rpc_server_manager.cpp deleted file mode 100644 index a9a748323f7..00000000000 --- a/slobrok/src/vespa/slobrok/server/rpc_server_manager.cpp +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "rpc_server_manager.h" -#include "reserved_name.h" -#include "rpc_server_map.h" -#include "remote_slobrok.h" -#include "sbenv.h" -#include <vespa/vespalib/util/stringfmt.h> -#include <sstream> - -#include <vespa/log/log.h> -LOG_SETUP(".slobrok.server.rpc_server_manager"); - -using vespalib::make_string_short::fmt; - -namespace slobrok { - -RpcServerManager::RpcServerManager(SBEnv &sbenv) - : FNET_Task(sbenv.getScheduler()), - _rpcsrvmap(sbenv.rpcServerMap()), - _exchanger(sbenv.exchangeManager()), - _env(sbenv), - _addManageds(), - _deleteList() -{ -} - -static OkState -validateName(const std::string & rpcsrvname) -{ - const char *p = rpcsrvname.c_str(); - while (*p != '\0') { - // important: disallow '*' - if (strchr("+,-./:=@[]_{}~<>" - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz", *p) == nullptr) - { - std::ostringstream tmp; - tmp << "Illegal character '" << *p << "' ("; - tmp << (int)(*p)<< ") in rpcserver name"; - return OkState(13, tmp.str().c_str()); - } - ++p; - } - if (p == rpcsrvname) { - return OkState(13, "empty rpcserver name"); - } - return OkState(); -} - - -OkState -RpcServerManager::checkPartner(const std::string & remslobrok) -{ - if (remslobrok == _env.mySpec()) { - return OkState(13, "remote slobrok using my rpcserver name"); - } - const RemoteSlobrok *partner = _exchanger.lookupPartner(remslobrok); - if (partner == nullptr) { - return OkState(13, "remote slobrok not a partner"); - } - return OkState(); -} - -OkState -RpcServerManager::addRemReservation(const std::string & remslobrok, const std::string & name, const std::string &spec) -{ - OkState state = checkPartner(remslobrok); - if (state.failed()) return state; - - OkState valid = validateName(name); - if (valid.failed()) return valid; - - const NamedService *old = _rpcsrvmap.lookupManaged(name); - if (old != nullptr) { - if (old->getSpec() == spec) { - // was alright already - return OkState(0, "already registered"); - } - LOG(warning, "remote %s tried to register [%s -> %s] but we already have [%s -> %s] registered!", - remslobrok.c_str(), name.c_str(), spec.c_str(), old->getName().c_str(), old->getSpec().c_str()); - return OkState(FRTE_RPC_METHOD_FAILED, "already managed by me"); - } - if (_rpcsrvmap.conflictingReservation(name, spec)) { - return OkState(FRTE_RPC_METHOD_FAILED, "registration for name already in progress"); - } - _rpcsrvmap.addReservation(std::make_unique<ReservedName>(name, spec, false)); - return OkState(0, "done"); -} - - -OkState -RpcServerManager::addMyReservation(const std::string & name, const std::string & spec) -{ - OkState valid = validateName(name); - if (valid.failed()) return valid; - - const NamedService *old = _rpcsrvmap.lookupManaged(name); - if (old != nullptr) { - if (old->getSpec() == spec) { - // was alright already - return OkState(0, "already registered"); - } else { - return OkState(FRTE_RPC_METHOD_FAILED, fmt("name %s registered (to %s), cannot register %s", - name.c_str(), old->getSpec().c_str(), spec.c_str())); - } - } - - // check if we already are in the progress of adding this - if (_rpcsrvmap.conflictingReservation(name, spec)) { - const ReservedName * rsv = _rpcsrvmap.getReservation(name); - LOG(warning, "conflicting registrations: wanted [%s -> %s] but [%s -> %s] already reserved", - name.c_str(), spec.c_str(), rsv->getName().c_str(), rsv->getSpec().c_str()); - return OkState(FRTE_RPC_METHOD_FAILED, - "registration for name already in progress with a different spec"); - } - _rpcsrvmap.removeReservation(name); - _rpcsrvmap.addReservation(std::make_unique<ReservedName>(name, spec, true)); - return OkState(0, "done"); -} - - -OkState -RpcServerManager::addRemote(const std::string & name, const std::string &spec) -{ - OkState valid = validateName(name); - if (valid.failed()) return valid; - - if (alreadyManaged(name, spec)) { - return OkState(0, "already correct"); - } - const NamedService *old = _rpcsrvmap.lookup(name); - if (old != nullptr) { - if (old->getSpec() != spec) { - LOG(warning, "collision on remote add: name %s registered to %s locally, " - "but another location broker wants it registered to %s", - name.c_str(), old->getSpec().c_str(), spec.c_str()); - removeRemote(name, old->getSpec()); - return OkState(13, "registered, with different spec"); - } - // was alright already, remove reservation - _rpcsrvmap.removeReservation(name); - return OkState(0, "already correct"); - } - _rpcsrvmap.removeReservation(name); - auto rpcsrv = std::make_unique<ManagedRpcServer>(name, spec, *this); - _rpcsrvmap.addNew(std::move(rpcsrv)); - return OkState(0, "done"); -} - -OkState -RpcServerManager::remove(ManagedRpcServer *rpcsrv) -{ - const NamedService *td = _rpcsrvmap.lookup(rpcsrv->getName()); - if (td == rpcsrv) { - return removeLocal(rpcsrv->getName(), rpcsrv->getSpec()); - } else { - return OkState(1, "not currently registered"); - } -} - - -OkState -RpcServerManager::removeRemote(const std::string &name, const std::string &spec) -{ - const NamedService *old = _rpcsrvmap.lookup(name); - if (old == nullptr) { - // was alright already, remove any reservation too - _rpcsrvmap.removeReservation(name); - return OkState(0, "already done"); - } - if (old->getSpec() != spec) { - return OkState(1, "name registered, but with different spec"); - } - std::unique_ptr<NamedService> td = _rpcsrvmap.remove(name); - LOG_ASSERT(td.get() == old); - return OkState(0, "done"); -} - -OkState -RpcServerManager::removeLocal(const std::string & name, const std::string &spec) -{ - const NamedService *td = _rpcsrvmap.lookup(name); - if (td == nullptr) { - // already removed, nop - return OkState(); - } - - const RemoteSlobrok *partner = _exchanger.lookupPartner(name); - if (partner != nullptr) { - return OkState(13, "cannot unregister partner slobrok"); - } - - const ManagedRpcServer *rpcsrv = _rpcsrvmap.lookupManaged(name); - if (rpcsrv == nullptr) { - return OkState(13, "not a local rpcserver"); - } - - if (rpcsrv->getSpec() != spec) { - // the client can probably ignore this "error" - // or log it on level INFO? - return OkState(1, fmt("name registered, but with different spec (%s)", rpcsrv->getSpec().c_str())); - } - auto tdUP = _rpcsrvmap.remove(name); - LOG_ASSERT(tdUP.get() == rpcsrv); - _exchanger.forwardRemove(name, spec); - return OkState(); -} - - -void -RpcServerManager::addManaged(ScriptCommand rdc) -{ - const std::string &name = rdc.name(); - const std::string &spec = rdc.spec(); - auto newRpcServer = std::make_unique<ManagedRpcServer>(name, spec, *this); - ManagedRpcServer & rpcsrv = *newRpcServer; - _rpcsrvmap.addNew(std::move(newRpcServer)); - for (size_t i = 0; i < _addManageds.size(); i++) { - if (_addManageds[i].rpcsrv == nullptr) { - _addManageds[i].rpcsrv = &rpcsrv; - _addManageds[i].handler = std::move(rdc); - rpcsrv.healthCheck(); - return; - } - } - _addManageds.emplace_back(&rpcsrv, std::move(rdc)); - rpcsrv.healthCheck(); - return; -} - - - -bool -RpcServerManager::alreadyManaged(const std::string &name, const std::string &spec) -{ - const ManagedRpcServer *rpcsrv = _rpcsrvmap.lookupManaged(name); - if (rpcsrv != nullptr) { - if (rpcsrv->getSpec() == spec) { - return true; - } - } - return false; -} - - -RpcServerManager::~RpcServerManager() -{ - Kill(); - PerformTask(); -} - - -void -RpcServerManager::PerformTask() -{ - std::vector<std::unique_ptr<NamedService>> deleteAfterSwap; - std::swap(deleteAfterSwap, _deleteList); -} - - -void -RpcServerManager::notifyFailedRpcSrv(ManagedRpcServer *rpcsrv, std::string errmsg) -{ - _env.countFailedHeartbeat(); - const auto &name = rpcsrv->getName(); - const auto &spec = rpcsrv->getSpec(); - const char *namep = name.c_str(); - const char *specp = spec.c_str(); - std::unique_ptr<NamedService> toDelete; - const NamedService *old = _rpcsrvmap.lookup(rpcsrv->getName()); - if (old == rpcsrv) { - toDelete = _rpcsrvmap.remove(name); - LOG_ASSERT(toDelete.get() == rpcsrv); - LOG(info, "managed server %s at %s failed: %s", namep, specp, errmsg.c_str()); - } else { - // only managed servers should exist, this is bad: - LOG(error, "unmanaged server %s at %s failed: %s", namep, specp, errmsg.c_str()); - } - _exchanger.forwardRemove(name, spec); - for (size_t i = 0; i < _addManageds.size(); ++i) { - if (_addManageds[i].rpcsrv == rpcsrv) { - LOG(warning, "rpcserver %s at %s failed while trying to register", namep, specp); - _addManageds[i].rpcsrv = nullptr; - _addManageds[i].handler.doneHandler(OkState(13, "failed check using listNames callback")); - } - } - if (toDelete) { - _deleteList.push_back(std::move(toDelete)); - ScheduleNow(); - } -} - -void -RpcServerManager::notifyOkRpcSrv(ManagedRpcServer *rpcsrv) -{ - for (size_t i = 0; i < _addManageds.size(); ++i) { - if (_addManageds[i].rpcsrv == rpcsrv) { - _addManageds[i].handler.doneHandler(OkState()); - _addManageds[i].rpcsrv = 0; - } - } - // XXX check if pending wantAdd / doAdd / registerRpcServer -} - -FRT_Supervisor * -RpcServerManager::getSupervisor() -{ - return _env.getSupervisor(); -} - -//----------------------------------------------------------------------------- - -} // namespace slobrok diff --git a/slobrok/src/vespa/slobrok/server/rpc_server_manager.h b/slobrok/src/vespa/slobrok/server/rpc_server_manager.h deleted file mode 100644 index 15b674388e3..00000000000 --- a/slobrok/src/vespa/slobrok/server/rpc_server_manager.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#pragma once - -#include "ok_state.h" -#include "cmd.h" -#include "i_rpc_server_manager.h" -#include "named_service.h" -#include <vespa/fnet/task.h> -#include <vector> -#include <memory> - -namespace slobrok { - -class NamedService; -class ManagedRpcServer; -class RemoteSlobrok; -class ReservedName; -class RpcServerMap; -class ExchangeManager; -class SBEnv; - -/** - * @class RpcServerManager - * @brief Main "business logic" for the service location broker. - * - * Used by all external and some internal operations. - * This class actually implements operations, - * checking for validity, manipulating internal datastructures, - * and initiating synchronization operations to peer slobroks. - **/ -class RpcServerManager : public FNET_Task, - public IRpcServerManager -{ -private: - RpcServerMap &_rpcsrvmap; - ExchangeManager &_exchanger; - SBEnv &_env; - - struct MRSandRRSC { - ManagedRpcServer *rpcsrv; - ScriptCommand handler; - MRSandRRSC(ManagedRpcServer *d, ScriptCommand h) - : rpcsrv(d), handler(std::move(h)) {} - }; - std::vector<MRSandRRSC> _addManageds; - std::vector<std::unique_ptr<NamedService>> _deleteList; -public: - OkState checkPartner(const std::string & remslobrok); - - OkState addRemote(const std::string & name, const std::string & spec); - - OkState addRemReservation(const std::string & remslobrok, const std::string & name, const std::string & spec); - OkState addMyReservation(const std::string & name, const std::string & spec); - - bool alreadyManaged(const std::string & name, const std::string & spec); - void addManaged(ScriptCommand rdc); - - OkState remove(ManagedRpcServer *rpcsrv); - - OkState removeLocal(const std::string & name, const std::string & spec); - OkState removeRemote(const std::string & name, const std::string & spec); - - RpcServerManager(const RpcServerManager &) = delete; - RpcServerManager &operator=(const RpcServerManager &) = delete; - RpcServerManager(SBEnv &sbenv); - ~RpcServerManager(); - - void PerformTask() override; - void notifyFailedRpcSrv(ManagedRpcServer *rpcsrv, std::string errmsg) override; - void notifyOkRpcSrv(ManagedRpcServer *rpcsrv) override; - FRT_Supervisor *getSupervisor() override; -}; - -//----------------------------------------------------------------------------- - -} // namespace slobrok - diff --git a/slobrok/src/vespa/slobrok/server/rpc_server_map.cpp b/slobrok/src/vespa/slobrok/server/rpc_server_map.cpp deleted file mode 100644 index fcaaf57570c..00000000000 --- a/slobrok/src/vespa/slobrok/server/rpc_server_map.cpp +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "rpc_server_map.h" -#include "reserved_name.h" -#include "rpc_server_manager.h" -#include "sbenv.h" - -#include <vespa/log/log.h> -LOG_SETUP(".slobrok.server.rpc_server_map"); - -namespace slobrok { - -//----------------------------------------------------------------------------- - -ManagedRpcServer * -RpcServerMap::lookupManaged(const std::string & name) const { - auto found = _myrpcsrv_map.find(name); - return (found == _myrpcsrv_map.end()) ? nullptr : found->second.get(); -} - -const NamedService * -RpcServerMap::lookup(const std::string & name) const -{ - return lookupManaged(name); -} - -std::unique_ptr<NamedService> -RpcServerMap::remove(const std::string & name) -{ - auto service = std::move(_myrpcsrv_map[name]); - auto spec = service->getSpec(); - _proxy.remove(ServiceMapping{name, spec}); - _myrpcsrv_map.erase(name); - return service; -} - -std::vector<const NamedService *> -RpcServerMap::allManaged() const -{ - std::vector<const NamedService *> retval; - // get list of all names in myrpcsrv_map - for (const auto & entry : _myrpcsrv_map) { - retval.push_back(entry.second.get()); - } - return retval; -} - - -void -RpcServerMap::add(NamedService *rpcsrv) -{ - const std::string &name = rpcsrv->getName(); - - LOG_ASSERT(rpcsrv != nullptr); - LOG_ASSERT(_myrpcsrv_map.find(name) == _myrpcsrv_map.end()); - - removeReservation(name); - _proxy.add(ServiceMapping{name, rpcsrv->getSpec()}); -} - -void -RpcServerMap::addNew(std::unique_ptr<ManagedRpcServer> rpcsrv) -{ - const std::string &name = rpcsrv->getName(); - - auto oldman = std::move(_myrpcsrv_map[name]); - _myrpcsrv_map.erase(name); - - if (oldman) { - const ReservedName *oldres = _reservations[name].get(); - const std::string &spec = rpcsrv->getSpec(); - _proxy.remove(ServiceMapping{name, spec}); - const std::string &oldname = oldman->getName(); - const std::string &oldspec = oldman->getSpec(); - if (spec != oldspec) { - LOG(warning, "internal state problem: adding [%s at %s] but already had [%s at %s]", - name.c_str(), spec.c_str(), oldname.c_str(), oldspec.c_str()); - if (oldres != nullptr) { - const std::string &n = oldres->getName(); - const std::string &s = oldres->getSpec(); - LOG(warning, "old reservation: [%s at %s]", n.c_str(), s.c_str()); - } - } - } - add(rpcsrv.get()); - _myrpcsrv_map[name] = std::move(rpcsrv); -} - - -void -RpcServerMap::addReservation(std::unique_ptr<ReservedName> rpcsrv) -{ - LOG_ASSERT(rpcsrv != nullptr); - LOG_ASSERT(_myrpcsrv_map.find(rpcsrv->getName()) == _myrpcsrv_map.end()); - - // must not be reserved for something else already - // this should have been checked already, so assert - LOG_ASSERT(! conflictingReservation(rpcsrv->getName(), rpcsrv->getSpec())); - auto old = std::move(_reservations[rpcsrv->getName()]); - LOG_ASSERT(!old - || old->getSpec() == rpcsrv->getSpec() - || ! old->stillReserved()); - _reservations[rpcsrv->getName()] = std::move(rpcsrv); -} - - -/** check if there is a (different) registration for this name in progress */ -bool -RpcServerMap::conflictingReservation(const std::string &name, const std::string &spec) -{ - const ReservedName *resv = _reservations[name].get(); - return (resv != nullptr && - resv->stillReserved() && - resv->getSpec() != spec); -} - -const ReservedName * -RpcServerMap::getReservation(const std::string &name) const { - auto found = _reservations.find(name); - return (found == _reservations.end()) ? nullptr : found->second.get(); -} - -RpcServerMap::RpcServerMap() = default; - -RpcServerMap::~RpcServerMap() = default; - -void -RpcServerMap::removeReservation(const std::string & name) -{ - _reservations.erase(name); -} - -} // namespace slobrok diff --git a/slobrok/src/vespa/slobrok/server/rpc_server_map.h b/slobrok/src/vespa/slobrok/server/rpc_server_map.h deleted file mode 100644 index 3d2999069ea..00000000000 --- a/slobrok/src/vespa/slobrok/server/rpc_server_map.h +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#pragma once - -#include "named_service.h" -#include "service_map_history.h" -#include "proxy_map_source.h" - -#include <memory> -#include <string> -#include <unordered_map> - -namespace slobrok { - -class NamedService; -class ManagedRpcServer; -class ReservedName; - -/** - * @class RpcServerMap - * @brief Contains the actual collections of NamedService (and subclasses) - * objects known by this location broker. - * - * Works as a collection of NamedService objects, but actually contains - * three seperate hashmaps. - **/ - -class RpcServerMap -{ -private: - using ManagedRpcServerMap = std::unordered_map<std::string, std::unique_ptr<ManagedRpcServer>>; - using ReservedNameMap = std::unordered_map<std::string, std::unique_ptr<ReservedName>>; - ManagedRpcServerMap _myrpcsrv_map; - ReservedNameMap _reservations; - ProxyMapSource _proxy; - - void add(NamedService *rpcsrv); - -public: - typedef std::vector<const NamedService *> RpcSrvlist; - - MapSource &proxy() { return _proxy; } - - ManagedRpcServer *lookupManaged(const std::string & name) const; - - const NamedService * lookup(const std::string & name) const; - RpcSrvlist allManaged() const; - - void addNew(std::unique_ptr<ManagedRpcServer> rpcsrv); - std::unique_ptr<NamedService> remove(const std::string & name); - - void addReservation(std::unique_ptr<ReservedName>rpcsrv); - bool conflictingReservation(const std::string & name, const std::string &spec); - - const ReservedName *getReservation(const std::string & name) const; - void removeReservation(const std::string & name); - - RpcServerMap(const RpcServerMap &) = delete; - RpcServerMap &operator=(const RpcServerMap &) = delete; - RpcServerMap(); - ~RpcServerMap(); -}; - -//----------------------------------------------------------------------------- - -} // namespace slobrok - diff --git a/slobrok/src/vespa/slobrok/server/rpchooks.cpp b/slobrok/src/vespa/slobrok/server/rpchooks.cpp index 540060210ed..6ce24be9201 100644 --- a/slobrok/src/vespa/slobrok/server/rpchooks.cpp +++ b/slobrok/src/vespa/slobrok/server/rpchooks.cpp @@ -4,8 +4,6 @@ #include "ok_state.h" #include "named_service.h" #include "request_completion_handler.h" -#include "rpc_server_map.h" -#include "rpc_server_manager.h" #include "remote_slobrok.h" #include "sbenv.h" #include "rpcmirror.h" @@ -38,12 +36,31 @@ public: ~MetricsReport() override { Kill(); } }; +bool match(const char *name, const char *pattern) { + LOG_ASSERT(name != nullptr); + LOG_ASSERT(pattern != nullptr); + while (*pattern != '\0') { + if (*name == *pattern) { + ++name; + ++pattern; + } else if (*pattern == '*') { + ++pattern; + while (*name != '/' && *name != '\0') { + ++name; + } + } else { + return false; + } + } + return (*name == *pattern); +} + } // namespace <unnamed> //----------------------------------------------------------------------------- -RPCHooks::RPCHooks(SBEnv &env, RpcServerMap& rpcsrvmap, RpcServerManager& rpcsrvman) - : _env(env), _rpcsrvmap(rpcsrvmap), _rpcsrvmanager(rpcsrvman), +RPCHooks::RPCHooks(SBEnv &env) + : _env(env), _globalHistory(env.globalHistory()), _localHistory(env.localHistory()), _cnts(Metrics::zero()), @@ -65,25 +82,6 @@ void RPCHooks::reportMetrics() { EV_COUNT("other_reqs", _cnts.otherReqs); } -bool RPCHooks::match(const char *name, const char *pattern) { - LOG_ASSERT(name != nullptr); - LOG_ASSERT(pattern != nullptr); - while (*pattern != '\0') { - if (*name == *pattern) { - ++name; - ++pattern; - } else if (*pattern == '*') { - ++pattern; - while (*name != '/' && *name != '\0') { - ++name; - } - } else { - return false; - } - } - return (*name == *pattern); -} - void RPCHooks::initRPC(FRT_Supervisor *supervisor) { _m_reporter = std::make_unique<MetricsReport>(supervisor, *this); @@ -229,11 +227,6 @@ void RPCHooks::initRPC(FRT_Supervisor *supervisor) { //------------------------------------------------------------------------- } - -bool RPCHooks::useNewLogic() const { - return _env.useNewLogic(); -} - void RPCHooks::rpc_listNamesServed(FRT_RPCRequest *req) { FRT_Values &dst = *req->GetReturn(); FRT_StringValue *names = dst.AddStringArray(1); @@ -257,9 +250,6 @@ void RPCHooks::rpc_registerRpcServer(FRT_RPCRequest *req) { } req->Detach(); _env.localMonitorMap().addLocal(mapping, std::make_unique<RequestCompletionHandler>(req)); - // TODO: remove this - auto script = ScriptCommand::makeRegRpcSrvCmd(_env, dName, dSpec, nullptr); - script.doRequest(); return; } diff --git a/slobrok/src/vespa/slobrok/server/rpchooks.h b/slobrok/src/vespa/slobrok/server/rpchooks.h index b68eb9007a8..bd051df64f1 100644 --- a/slobrok/src/vespa/slobrok/server/rpchooks.h +++ b/slobrok/src/vespa/slobrok/server/rpchooks.h @@ -40,8 +40,6 @@ public: private: SBEnv &_env; - RpcServerMap &_rpcsrvmap; - RpcServerManager &_rpcsrvmanager; ServiceMapHistory &_globalHistory; ServiceMapHistory &_localHistory; @@ -49,44 +47,33 @@ private: std::unique_ptr<FNET_Task> _m_reporter; public: - RPCHooks(SBEnv &env, RpcServerMap& rpcsrvmap, RpcServerManager& rpcsrvman); + RPCHooks(SBEnv &env); ~RPCHooks() override; - static bool match(const char *name, const char *pattern); - void initRPC(FRT_Supervisor *supervisor); void reportMetrics(); const Metrics& getMetrics() const { return _cnts; } void countFailedHeartbeat() { _cnts.heartBeatFails++; } private: - bool useNewLogic() const; - - void rpc_lookupRpcServer(FRT_RPCRequest *req); - - void new_registerRpcServer(FRT_RPCRequest *req); - void new_unregisterRpcServer(FRT_RPCRequest *req); - void new_wantAdd(FRT_RPCRequest *req); - void new_doRemove(FRT_RPCRequest *req); - void new_doAdd(FRT_RPCRequest *req); - void rpc_registerRpcServer(FRT_RPCRequest *req); void rpc_unregisterRpcServer(FRT_RPCRequest *req); - void rpc_addPeer(FRT_RPCRequest *req); void rpc_removePeer(FRT_RPCRequest *req); + void rpc_incrementalFetch(FRT_RPCRequest *req); + void rpc_doRemove(FRT_RPCRequest *req); + void rpc_fetchLocalView(FRT_RPCRequest *req); + void rpc_listNamesServed(FRT_RPCRequest *req); + + /** for unit tests and debugging, consider removing some of these: */ + void rpc_lookupRpcServer(FRT_RPCRequest *req); void rpc_listManagedRpcServers(FRT_RPCRequest *req); void rpc_lookupManaged(FRT_RPCRequest *req); void rpc_listAllRpcServers(FRT_RPCRequest *req); - void rpc_incrementalFetch(FRT_RPCRequest *req); void rpc_wantAdd(FRT_RPCRequest *req); void rpc_doAdd(FRT_RPCRequest *req); - void rpc_doRemove(FRT_RPCRequest *req); - void rpc_fetchLocalView(FRT_RPCRequest *req); - - void rpc_listNamesServed(FRT_RPCRequest *req); - void rpc_getRpcServerHistory(FRT_RPCRequest *req); + /** consider removing: */ void rpc_stop(FRT_RPCRequest *req); void rpc_version(FRT_RPCRequest *req); }; diff --git a/slobrok/src/vespa/slobrok/server/sbenv.cpp b/slobrok/src/vespa/slobrok/server/sbenv.cpp index 42debe1556c..1d279994cb0 100644 --- a/slobrok/src/vespa/slobrok/server/sbenv.cpp +++ b/slobrok/src/vespa/slobrok/server/sbenv.cpp @@ -97,19 +97,16 @@ ConfigTask::PerformTask() } // namespace slobrok::<unnamed> -SBEnv::SBEnv(const ConfigShim &shim) : SBEnv(shim, true) {} - -SBEnv::SBEnv(const ConfigShim &shim, bool) +SBEnv::SBEnv(const ConfigShim &shim) : _transport(std::make_unique<FNET_Transport>(TransportConfig().drop_empty_buffers(true))), _supervisor(std::make_unique<FRT_Supervisor>(_transport.get())), _configShim(shim), _configurator(shim.factory().create(*this)), _shuttingDown(false), - _useNewLogic(true), _partnerList(), _me(createSpec(_configShim.portNumber())), - _rpcHooks(*this, _rpcsrvmap, _rpcsrvmanager), - _remotechecktask(std::make_unique<RemoteCheck>(getSupervisor()->GetScheduler(), _rpcsrvmap, _rpcsrvmanager, _exchanger)), + _rpcHooks(*this), + _remotechecktask(std::make_unique<RemoteCheck>(getSupervisor()->GetScheduler(), _exchanger)), _health(), _metrics(_rpcHooks, *_transport), _components(), @@ -117,9 +114,7 @@ SBEnv::SBEnv(const ConfigShim &shim, bool) [this] (MappingMonitorOwner &owner) { return std::make_unique<RpcMappingMonitor>(*_supervisor, owner); }), - _rpcsrvmanager(*this), - _exchanger(*this, _rpcsrvmap), - _rpcsrvmap() + _exchanger(*this) { srandom(time(nullptr) ^ getpid()); // note: feedback loop between these two: @@ -201,7 +196,6 @@ SBEnv::MainLoop() return 0; } - void SBEnv::setup(const std::vector<std::string> &cfg) { @@ -272,7 +266,7 @@ SBEnv::removePeer(const std::string &name, const std::string &spec) if (partner == nullptr) { return OkState(0, "remote slobrok not a partner"); } - _exchanger.removePartner(name); + _exchanger.removePartner(spec); return OkState(0, "done"); } diff --git a/slobrok/src/vespa/slobrok/server/sbenv.h b/slobrok/src/vespa/slobrok/server/sbenv.h index c6fd8905131..09986e037a9 100644 --- a/slobrok/src/vespa/slobrok/server/sbenv.h +++ b/slobrok/src/vespa/slobrok/server/sbenv.h @@ -3,8 +3,6 @@ #include "named_service.h" #include "rpc_mapping_monitor.h" -#include "rpc_server_map.h" -#include "rpc_server_manager.h" #include "remote_slobrok.h" #include "exchange_manager.h" #include "configshim.h" @@ -44,7 +42,6 @@ private: ConfigShim _configShim; Configurator::UP _configurator; bool _shuttingDown; - const bool _useNewLogic; SBEnv(const SBEnv &); // Not used SBEnv &operator=(const SBEnv &); // Not used @@ -62,9 +59,7 @@ private: UnionServiceMap _consensusMap; ServiceMapHistory _globalVisibleHistory; - RpcServerManager _rpcsrvmanager; ExchangeManager _exchanger; - RpcServerMap _rpcsrvmap; std::unique_ptr<MapSubscription> _localMonitorSubscription; std::unique_ptr<MapSubscription> _consensusSubscription; @@ -72,7 +67,6 @@ private: public: explicit SBEnv(const ConfigShim &shim); - SBEnv(const ConfigShim &shim, bool useNewConsensusLogic); ~SBEnv(); FNET_Transport *getTransport() { return _transport.get(); } @@ -83,9 +77,7 @@ public: void suspend(); void resume(); - RpcServerManager& rpcServerManager() { return _rpcsrvmanager; } ExchangeManager& exchangeManager() { return _exchanger; } - RpcServerMap& rpcServerMap() { return _rpcsrvmap; } ServiceMapHistory& globalHistory() { return _globalVisibleHistory; @@ -107,12 +99,11 @@ public: bool isSuspended() const { return false; } bool isShuttingDown() const { return _shuttingDown; } - bool useNewLogic() const { return _useNewLogic; } int MainLoop(); - OkState addPeer(const std::string& name, const std::string &spec); - OkState removePeer(const std::string& name, const std::string &spec); + OkState addPeer(const std::string& name, const std::string& spec); + OkState removePeer(const std::string& name, const std::string& spec); void countFailedHeartbeat() { _rpcHooks.countFailedHeartbeat(); } }; diff --git a/standalone-container/src/main/sh/standalone-container.sh b/standalone-container/src/main/sh/standalone-container.sh index b30a7ad4cd0..e6cbb435b6f 100755 --- a/standalone-container/src/main/sh/standalone-container.sh +++ b/standalone-container/src/main/sh/standalone-container.sh @@ -180,7 +180,6 @@ StartCommand() { -Djdisc.config.file="$cfpfile" \ -Djdisc.export.packages= \ -Djdisc.cache.path="$bundlecachedir" \ - -Djdisc.debug.resources=false \ -Djdisc.bundle.path="$VESPA_HOME/lib/jars" \ -Djdisc.logger.enabled=true \ -Djdisc.logger.level=ALL \ diff --git a/storage/src/tests/distributor/distributor_stripe_test.cpp b/storage/src/tests/distributor/distributor_stripe_test.cpp index 418b224eb25..7fdc2578545 100644 --- a/storage/src/tests/distributor/distributor_stripe_test.cpp +++ b/storage/src/tests/distributor/distributor_stripe_test.cpp @@ -230,6 +230,25 @@ TEST_F(DistributorStripeTest, operations_generated_and_started_without_duplicate ASSERT_EQ(6, _sender.commands().size()); } +TEST_F(DistributorStripeTest, maintenance_scheduling_inhibited_if_cluster_state_is_pending) +{ + setup_stripe(Redundancy(2), NodeCount(4), "storage:3 distributor:1"); + simulate_set_pending_cluster_state("storage:4 distributor:1"); + + _sender.commands().clear(); // Remove pending bucket info requests + + tickDistributorNTimes(1); + EXPECT_FALSE(stripe_is_in_recovery_mode()); + + for (uint32_t i = 0; i < 6; ++i) { + addNodesToBucketDB(document::BucketId(16, i), "0=2"); // Needs activation, merging + } + tickDistributorNTimes(10); + + // No ops should have been actually generated + ASSERT_EQ(0, _sender.commands().size()); +} + TEST_F(DistributorStripeTest, recovery_mode_on_cluster_state_change) { setup_stripe(Redundancy(1), NodeCount(2), diff --git a/storage/src/vespa/storage/config/distributorconfiguration.h b/storage/src/vespa/storage/config/distributorconfiguration.h index 7aa10893b80..09b30db086a 100644 --- a/storage/src/vespa/storage/config/distributorconfiguration.h +++ b/storage/src/vespa/storage/config/distributorconfiguration.h @@ -1,8 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #pragma once -#include "config-stor-distributormanager.h" -#include "config-stor-visitordispatcher.h" +#include <vespa/storage/config/config-stor-distributormanager.h> +#include <vespa/storage/config/config-stor-visitordispatcher.h> #include <vespa/vespalib/stllike/hash_set.h> #include <vespa/storage/common/storagecomponent.h> #include <vespa/vespalib/util/time.h> diff --git a/storage/src/vespa/storage/distributor/distributor_bucket_space.cpp b/storage/src/vespa/storage/distributor/distributor_bucket_space.cpp index 9ec4d31eb32..3cfb8d70b7d 100644 --- a/storage/src/vespa/storage/distributor/distributor_bucket_space.cpp +++ b/storage/src/vespa/storage/distributor/distributor_bucket_space.cpp @@ -50,7 +50,7 @@ DistributorBucketSpace::enumerate_available_nodes() _distribution_bits = _clusterState->getDistributionBitCount(); auto node_count = _clusterState->getNodeCount(lib::NodeType::STORAGE); if (_pending_cluster_state) { - _distribution_bits = std::min(_distribution_bits, _pending_cluster_state->getDistributionBitCount()); + _distribution_bits = std::max(_distribution_bits, _pending_cluster_state->getDistributionBitCount()); node_count = std::min(node_count, _pending_cluster_state->getNodeCount(lib::NodeType::STORAGE)); } std::vector<bool> nodes(node_count); diff --git a/storage/src/vespa/storage/storageserver/rpc/rpc_envelope_proto.h b/storage/src/vespa/storage/storageserver/rpc/rpc_envelope_proto.h index dbc048cd504..7e065ab0739 100644 --- a/storage/src/vespa/storage/storageserver/rpc/rpc_envelope_proto.h +++ b/storage/src/vespa/storage/storageserver/rpc/rpc_envelope_proto.h @@ -7,6 +7,6 @@ #pragma GCC diagnostic ignored "-Wsuggest-override" #endif -#include "rpc_envelope.pb.h" +#include <vespa/storage/storageserver/rpc/rpc_envelope.pb.h> #pragma GCC diagnostic pop diff --git a/storageapi/src/vespa/storageapi/mbusprot/protobuf_includes.h b/storageapi/src/vespa/storageapi/mbusprot/protobuf_includes.h index 289e5dc355c..f5111ae8061 100644 --- a/storageapi/src/vespa/storageapi/mbusprot/protobuf_includes.h +++ b/storageapi/src/vespa/storageapi/mbusprot/protobuf_includes.h @@ -8,9 +8,9 @@ #pragma GCC diagnostic ignored "-Wsuggest-override" #endif -#include "feed.pb.h" -#include "inspect.pb.h" -#include "visiting.pb.h" -#include "maintenance.pb.h" +#include <vespa/storageapi/mbusprot/feed.pb.h> +#include <vespa/storageapi/mbusprot/inspect.pb.h> +#include <vespa/storageapi/mbusprot/visiting.pb.h> +#include <vespa/storageapi/mbusprot/maintenance.pb.h> #pragma GCC diagnostic pop diff --git a/vdslib/src/main/java/com/yahoo/vdslib/state/ClusterState.java b/vdslib/src/main/java/com/yahoo/vdslib/state/ClusterState.java index c536fc0f4cd..8704b11fdfb 100644 --- a/vdslib/src/main/java/com/yahoo/vdslib/state/ClusterState.java +++ b/vdslib/src/main/java/com/yahoo/vdslib/state/ClusterState.java @@ -3,12 +3,13 @@ package com.yahoo.vdslib.state; import com.yahoo.text.StringUtilities; import java.text.ParseException; -import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.StringTokenizer; -import java.util.TreeMap; -import java.util.TreeSet; /** * Be careful about changing this class, as it mirrors the ClusterState in C++. @@ -18,25 +19,183 @@ public class ClusterState implements Cloneable { private static final NodeState DEFAULT_STORAGE_UP_NODE_STATE = new NodeState(NodeType.STORAGE, State.UP); private static final NodeState DEFAULT_DISTRIBUTOR_UP_NODE_STATE = new NodeState(NodeType.DISTRIBUTOR, State.UP); + private static final NodeState DEFAULT_STORAGE_DOWN_NODE_STATE = new NodeState(NodeType.STORAGE, State.DOWN); + private static final NodeState DEFAULT_DISTRIBUTOR_DOWN_NODE_STATE = new NodeState(NodeType.DISTRIBUTOR, State.DOWN); - private int version = 0; - private State state = State.DOWN; - // nodeStates maps each of the non-up nodes that have an index <= the node count for its type. - private Map<Node, NodeState> nodeStates = new TreeMap<>(); + /** + * Maintains a bitset where all non-down nodes have a bit set. All nodes that differs from defaultUp + * and defaultDown are store explicit in a hash map. + */ + private static class Nodes { + private int logicalNodeCount; + private final NodeType type; + private final BitSet upNodes; + private final Map<Integer, NodeState> nodeStates = new HashMap<>(); + Nodes(NodeType type) { + this.type = type; + upNodes = new BitSet(); + } + Nodes(Nodes b) { + logicalNodeCount = b.logicalNodeCount; + type = b.type; + upNodes = (BitSet) b.upNodes.clone(); + b.nodeStates.forEach((key, value) -> nodeStates.put(key, value.clone())); + } + + void updateMaxIndex(int index) { + if (index > logicalNodeCount) { + upNodes.set(logicalNodeCount, index); + logicalNodeCount = index; + } + } + + int getLogicalNodeCount() { return logicalNodeCount; } + + NodeState getNodeState(int index) { + NodeState ns = nodeStates.get(index); + if (ns != null) return ns; + return (index >= getLogicalNodeCount() || ! upNodes.get(index)) + ? new NodeState(type, State.DOWN) + : new NodeState(type, State.UP); + } + + private void validateInput(Node node, NodeState ns) { + ns.verifyValidInSystemState(node.getType()); + if (node.getType() != type) { + throw new IllegalArgumentException("NodeType '" + node.getType() + "' differs from '" + type + "'"); + } + } + + void setNodeState(Node node, NodeState ns) { + validateInput(node, ns); + int index = node.getIndex(); + if (index >= logicalNodeCount) { + logicalNodeCount = index + 1; + } + setNodeStateInternal(index, ns); + } + + void addNodeState(Node node, NodeState ns) { + validateInput(node, ns); + int index = node.getIndex(); + updateMaxIndex(index + 1); + setNodeStateInternal(index, ns); + } + + private static boolean equalsWithDescription(NodeState a, NodeState b) { + // This is due to NodeState.equals considers semantic equality, and description is not part of that. + return a.equals(b) && ((a.getState() != State.DOWN) || a.getDescription().equals(b.getDescription())); + } + + private void setNodeStateInternal(int index, NodeState ns) { + nodeStates.remove(index); + if (ns.getState() == State.DOWN) { + upNodes.clear(index); + if ( ! equalsWithDescription(defaultDown(), ns)) { + nodeStates.put(index, ns); + } + } else { + upNodes.set(index); + if ( ! equalsWithDescription(defaultUp(), ns)) { + nodeStates.put(index, ns); + } + } + } + + boolean similarToImpl(Nodes other, final NodeStateCmp nodeStateCmp) { + // TODO verify behavior of C++ impl against this + if (logicalNodeCount != other.logicalNodeCount) return false; + if (type != other.type) return false; + if ( ! upNodes.equals(other.upNodes)) return false; + for (Integer node : unionNodeSetWith(other.nodeStates.keySet())) { + final NodeState lhs = nodeStates.get(node); + final NodeState rhs = other.nodeStates.get(node); + if (!nodeStateCmp.similar(type, lhs, rhs)) { + return false; + } + } + return true; + } + + private Set<Integer> unionNodeSetWith(final Set<Integer> otherNodes) { + final Set<Integer> unionNodeSet = new HashSet<>(nodeStates.keySet()); + unionNodeSet.addAll(otherNodes); + return unionNodeSet; + } + + @Override + public String toString() { return toString(false); } + + String toString(boolean verbose) { + StringBuilder sb = new StringBuilder(); + + int nodeCount = verbose ? getLogicalNodeCount() : upNodes.length(); + if ( nodeCount > 0 ) { + sb.append(type == NodeType.DISTRIBUTOR ? " distributor:" : " storage:").append(nodeCount); + for (int i = 0; i < nodeCount; i++) { + String nodeState = getNodeState(i).serialize(i, verbose); + if (!nodeState.isEmpty()) { + sb.append(' ').append(nodeState); + } + } + } + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (! (obj instanceof Nodes)) return false; + Nodes b = (Nodes) obj; + if (logicalNodeCount != b.logicalNodeCount) return false; + if (type != b.type) return false; + if (!upNodes.equals(b.upNodes)) return false; + if (!nodeStates.equals(b.nodeStates)) return false; + return true; + } - // TODO: Change to one count for distributor and one for storage, rather than an array - // TODO: RenameFunction, this is not the highest node count but the highest index - private ArrayList<Integer> nodeCount = new ArrayList<>(2); + @Override + public int hashCode() { + return Objects.hash(logicalNodeCount, type, nodeStates, upNodes); + } + private NodeState defaultDown() { + return type == NodeType.STORAGE + ? DEFAULT_STORAGE_DOWN_NODE_STATE + : DEFAULT_DISTRIBUTOR_DOWN_NODE_STATE; + } + private NodeState defaultUp() { + return defaultUpNodeState(type); + } + } + private int version = 0; + private State state = State.DOWN; private String description = ""; private int distributionBits = 16; + private final Nodes distributorNodes; + private final Nodes storageNodes; + public ClusterState(String serialized) throws ParseException { - nodeCount.add(0); - nodeCount.add(0); + distributorNodes = new Nodes(NodeType.DISTRIBUTOR); + storageNodes = new Nodes(NodeType.STORAGE); deserialize(serialized); } + public ClusterState(ClusterState b) { + version = b.version; + state = b.state; + description = b.description; + distributionBits = b.distributionBits; + distributorNodes = new Nodes(b.distributorNodes); + storageNodes = new Nodes(b.storageNodes); + } + + private Nodes getNodes(NodeType type) { + return (type == NodeType.STORAGE) + ? storageNodes + : (type == NodeType.DISTRIBUTOR) ? distributorNodes : null; + } + /** * Parse a given cluster state string into a returned ClusterState instance, wrapping any * parse exceptions in a RuntimeException. @@ -54,20 +213,7 @@ public class ClusterState implements Cloneable { } public ClusterState clone() { - try{ - ClusterState state = (ClusterState) super.clone(); - state.nodeStates = new TreeMap<>(); - for (Map.Entry<Node, NodeState> entry : nodeStates.entrySet()) { - state.nodeStates.put(entry.getKey(), entry.getValue().clone()); - } - state.nodeCount = new ArrayList<>(2); - state.nodeCount.add(nodeCount.get(0)); - state.nodeCount.add(nodeCount.get(1)); - return state; - } catch (CloneNotSupportedException e) { - assert(false); // Should never happen - return null; - } + return new ClusterState(this); } @Override @@ -77,8 +223,8 @@ public class ClusterState implements Cloneable { if (version != other.version || !state.equals(other.state) || distributionBits != other.distributionBits - || !nodeCount.equals(other.nodeCount) - || !nodeStates.equals(other.nodeStates)) + || !distributorNodes.equals(other.distributorNodes) + || !storageNodes.equals(other.storageNodes)) { return false; } @@ -87,7 +233,7 @@ public class ClusterState implements Cloneable { @Override public int hashCode() { - return java.util.Objects.hash(version, state, distributionBits, nodeCount, nodeStates); + return java.util.Objects.hash(version, state, distributionBits, distributorNodes, storageNodes); } @FunctionalInterface @@ -106,7 +252,7 @@ public class ClusterState implements Cloneable { return similarToImpl(other, this::normalizedNodeStateSimilarToIgnoringInitProgress); } - private boolean similarToImpl(final ClusterState other, final NodeStateCmp nodeStateCmp) { + private boolean similarToImpl(ClusterState other, final NodeStateCmp nodeStateCmp) { if (other == this) { return true; // We're definitely similar to ourselves. } @@ -119,23 +265,11 @@ public class ClusterState implements Cloneable { if (!metaInformationSimilarTo(other)) { return false; } - // TODO verify behavior of C++ impl against this - for (Node node : unionNodeSetWith(other.nodeStates.keySet())) { - final NodeState lhs = nodeStates.get(node); - final NodeState rhs = other.nodeStates.get(node); - if (!nodeStateCmp.similar(node.getType(), lhs, rhs)) { - return false; - } - } + if ( !distributorNodes.similarToImpl(other.distributorNodes, nodeStateCmp)) return false; + if ( !storageNodes.similarToImpl(other.storageNodes, nodeStateCmp)) return false; return true; } - private Set<Node> unionNodeSetWith(final Set<Node> otherNodes) { - final Set<Node> unionNodeSet = new TreeSet<>(nodeStates.keySet()); - unionNodeSet.addAll(otherNodes); - return unionNodeSet; - } - private boolean metaInformationSimilarTo(final ClusterState other) { if (version != other.version || !state.equals(other.state)) { return false; @@ -143,7 +277,7 @@ public class ClusterState implements Cloneable { if (distributionBits != other.distributionBits) { return false; } - return nodeCount.equals(other.nodeCount); + return true; } private boolean normalizedNodeStateSimilarTo(final NodeType nodeType, final NodeState lhs, final NodeState rhs) { @@ -178,12 +312,7 @@ public class ClusterState implements Cloneable { void addNodeState() throws ParseException { if (!empty) { NodeState ns = NodeState.deserialize(node.getType(), sb.toString()); - if (!ns.equals(defaultUpNodeState(node.getType()))) { - nodeStates.put(node, ns); - } - if (nodeCount.get(node.getType().ordinal()) <= node.getIndex()) { - nodeCount.set(node.getType().ordinal(), node.getIndex() + 1); - } + getNodes(node.getType()).addNodeState(node, ns); } empty = true; sb = new StringBuilder(); @@ -257,9 +386,7 @@ public class ClusterState implements Cloneable { } catch (Exception e) { throw new ParseException("Illegal node count '" + value + "' in state: " + serialized, 0); } - if (nodeCount > this.nodeCount.get(nodeType.ordinal())) { - this.nodeCount.set(nodeType.ordinal(), nodeCount); - } + getNodes(nodeType).updateMaxIndex(nodeCount); continue; } int dot2 = key.indexOf('.', dot + 1); @@ -269,8 +396,8 @@ public class ClusterState implements Cloneable { } else { node = new Node(nodeType, Integer.valueOf(key.substring(dot + 1, dot2))); } - if (node.getIndex() >= this.nodeCount.get(nodeType.ordinal())) { - throw new ParseException("Cannot index " + nodeType + " node " + node.getIndex() + " of " + this.nodeCount.get(nodeType.ordinal()) + " in state: " + serialized, 0); + if (node.getIndex() >= getNodeCount(nodeType)) { + throw new ParseException("Cannot index " + nodeType + " node " + node.getIndex() + " of " + getNodeCount(nodeType) + " in state: " + serialized, 0); } if (!nodeData.node.equals(node)) { nodeData.addNodeState(); @@ -289,7 +416,6 @@ public class ClusterState implements Cloneable { // Ignore unknown nodeStates } nodeData.addNodeState(); - removeLastNodesDownWithoutReason(); } public String getTextualDifference(ClusterState other) { @@ -363,7 +489,7 @@ public class ClusterState implements Cloneable { * E.g. if node X is down and without description, but nodex X-1 is up, then Y is 1. * The node count for distributors is then X + 1 - Y. */ - public int getNodeCount(NodeType type) { return nodeCount.get(type.ordinal()); } + public int getNodeCount(NodeType type) { return getNodes(type).getLogicalNodeCount(); } /** * Returns the state of a node. @@ -371,9 +497,7 @@ public class ClusterState implements Cloneable { * and DOWN otherwise. */ public NodeState getNodeState(Node node) { - if (node.getIndex() >= nodeCount.get(node.getType().ordinal())) - return new NodeState(node.getType(), State.DOWN); - return nodeStates.getOrDefault(node, new NodeState(node.getType(), State.UP)); + return getNodes(node.getType()).getNodeState(node.getIndex()); } /** @@ -383,35 +507,7 @@ public class ClusterState implements Cloneable { */ public void setNodeState(Node node, NodeState newState) { newState.verifyValidInSystemState(node.getType()); - if (node.getIndex() >= nodeCount.get(node.getType().ordinal())) { - for (int i= nodeCount.get(node.getType().ordinal()); i<node.getIndex(); ++i) { - nodeStates.put(new Node(node.getType(), i), new NodeState(node.getType(), State.DOWN)); - } - nodeCount.set(node.getType().ordinal(), node.getIndex() + 1); - } - if (newState.equals(new NodeState(node.getType(), State.UP))) { - nodeStates.remove(node); - } else { - nodeStates.put(node, newState); - } - if (newState.getState().equals(State.DOWN)) { - // We might be setting the last node down, so we can remove some states - removeLastNodesDownWithoutReason(); - } - } - - private void removeLastNodesDownWithoutReason() { - for (NodeType nodeType : NodeType.values()) { - for (int index = nodeCount.get(nodeType.ordinal()) - 1; index >= 0; --index) { - Node node = new Node(nodeType, index); - NodeState nodeState = nodeStates.get(node); - if (nodeState == null) break; // Node not existing is up - if ( ! nodeState.getState().equals(State.DOWN)) break; // Node not down can not be removed - if (nodeState.hasDescription()) break; // Node have reason to be down. Don't remove node as we will forget reason - nodeStates.remove(node); - nodeCount.set(nodeType.ordinal(), node.getIndex()); - } - } + getNodes(node.getType()).setNodeState(node, newState); } public String getDescription() { return description; } @@ -440,35 +536,9 @@ public class ClusterState implements Cloneable { sb.append(" bits:").append(distributionBits); } - int distributorNodeCount = getNodeCount(NodeType.DISTRIBUTOR); - int storageNodeCount = getNodeCount(NodeType.STORAGE); - // If not printing verbose, we're not printing descriptions, so we can remove tailing nodes that are down that has descriptions too - if (!verbose) { - while (distributorNodeCount > 0 && getNodeState(new Node(NodeType.DISTRIBUTOR, distributorNodeCount - 1)).getState().equals(State.DOWN)) --distributorNodeCount; - while (storageNodeCount > 0 && getNodeState(new Node(NodeType.STORAGE, storageNodeCount - 1)).getState().equals(State.DOWN)) --storageNodeCount; - } - if (distributorNodeCount > 0){ - sb.append(" distributor:").append(distributorNodeCount); - for (Map.Entry<Node, NodeState> entry : nodeStates.entrySet()) { - if (entry.getKey().getType().equals(NodeType.DISTRIBUTOR) && entry.getKey().getIndex() < distributorNodeCount) { - String nodeState = entry.getValue().serialize(entry.getKey().getIndex(), verbose); - if (!nodeState.isEmpty()) { - sb.append(' ').append(nodeState); - } - } - } - } - if (storageNodeCount > 0){ - sb.append(" storage:").append(storageNodeCount); - for (Map.Entry<Node, NodeState> entry : nodeStates.entrySet()) { - if (entry.getKey().getType().equals(NodeType.STORAGE) && entry.getKey().getIndex() < storageNodeCount) { - String nodeState = entry.getValue().serialize(entry.getKey().getIndex(), verbose); - if (!nodeState.isEmpty()) { - sb.append(' ').append(nodeState); - } - } - } - } + sb.append(distributorNodes.toString(verbose)); + sb.append(storageNodes.toString(verbose)); + if (sb.length() > 0) { // Remove first space if not empty sb.deleteCharAt(0); } diff --git a/vdslib/src/main/java/com/yahoo/vdslib/state/NodeState.java b/vdslib/src/main/java/com/yahoo/vdslib/state/NodeState.java index 0edf30bc1ff..80cf87b7597 100644 --- a/vdslib/src/main/java/com/yahoo/vdslib/state/NodeState.java +++ b/vdslib/src/main/java/com/yahoo/vdslib/state/NodeState.java @@ -51,6 +51,7 @@ public class NodeState implements Cloneable { public boolean equals(Object o) { if (!(o instanceof NodeState)) { return false; } NodeState ns = (NodeState) o; + // Note that 'description' is not considered as it carries semantics. if (state != ns.state || Math.abs(capacity - ns.capacity) > 0.0000000001 || Math.abs(initProgress - ns.initProgress) > 0.0000000001 diff --git a/vdslib/src/test/java/com/yahoo/vdslib/state/ClusterStateTestCase.java b/vdslib/src/test/java/com/yahoo/vdslib/state/ClusterStateTestCase.java index dbc8888cfda..956ea216a44 100644 --- a/vdslib/src/test/java/com/yahoo/vdslib/state/ClusterStateTestCase.java +++ b/vdslib/src/test/java/com/yahoo/vdslib/state/ClusterStateTestCase.java @@ -4,6 +4,7 @@ package com.yahoo.vdslib.state; import org.junit.Test; import java.text.ParseException; +import java.util.BitSet; import java.util.function.BiFunction; import static org.junit.Assert.assertEquals; @@ -311,6 +312,17 @@ public class ClusterStateTestCase{ } @Test + public void testBitSet() { + BitSet b = new BitSet(); + assertEquals(0, b.length()); + b.set(7); + b.set(107); + assertEquals(108, b.length()); + b.clear(107); + assertEquals(8, b.length()); + } + + @Test public void testVersionAndClusterStates() throws ParseException { ClusterState state = new ClusterState("version:4 cluster:i distributor:2 .1.s:i storage:2 .0.s:i .0.i:0.345"); assertEquals(4, state.getVersion()); diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java index acf580d7e1b..3acd1c34d51 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java @@ -34,7 +34,7 @@ import java.util.logging.Logger; */ public abstract class ClientBase implements AutoCloseable { - private static final Logger logger = Logger.getLogger(ClientBase.class.getName()); + protected final Logger logger = Logger.getLogger(getClass().getName()); private static final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java index 297852e9584..769a2a54c95 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java @@ -1,7 +1,6 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.athenz.client.zms; -import com.yahoo.io.IOUtils; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzGroup; import com.yahoo.vespa.athenz.api.AthenzIdentity; @@ -18,7 +17,7 @@ import com.yahoo.vespa.athenz.client.zms.bindings.AssertionEntity; import com.yahoo.vespa.athenz.client.zms.bindings.DomainListResponseEntity; import com.yahoo.vespa.athenz.client.zms.bindings.MembershipEntity; import com.yahoo.vespa.athenz.client.zms.bindings.PolicyEntity; -import com.yahoo.vespa.athenz.client.zms.bindings.ProviderResourceGroupRolesRequestEntity; +import com.yahoo.vespa.athenz.client.zms.bindings.ResourceGroupRolesEntity; import com.yahoo.vespa.athenz.client.zms.bindings.ResponseListEntity; import com.yahoo.vespa.athenz.client.zms.bindings.RoleEntity; import com.yahoo.vespa.athenz.client.zms.bindings.ServiceEntity; @@ -33,11 +32,8 @@ import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHeader; import javax.net.ssl.SSLContext; -import java.io.IOException; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -104,7 +100,7 @@ public class DefaultZmsClient extends ClientBase implements ZmsClient { HttpUriRequest request = RequestBuilder.put() .setUri(uri) .addHeader(createCookieHeaderWithOktaTokens(identityToken, accessToken)) - .setEntity(toJsonStringEntity(new ProviderResourceGroupRolesRequestEntity(providerService, tenantDomain, roleActions, resourceGroup))) + .setEntity(toJsonStringEntity(new ResourceGroupRolesEntity(providerService, tenantDomain, roleActions, resourceGroup))) .build(); execute(request, response -> readEntity(response, Void.class)); // Note: The ZMS API will actually return a json object that is similar to ProviderResourceGroupRolesRequestEntity } @@ -121,6 +117,31 @@ public class DefaultZmsClient extends ClientBase implements ZmsClient { } @Override + public void createTenantResourceGroup(AthenzDomain tenantDomain, AthenzIdentity provider, String resourceGroup, + Set<RoleAction> roleActions) { + URI uri = zmsUrl.resolve(String.format("domain/%s/service/%s/tenant/%s/resourceGroup/%s", + provider.getDomainName(), provider.getName(), tenantDomain.getName(), resourceGroup)); + HttpUriRequest request = RequestBuilder.put() + .setUri(uri) + .setEntity(toJsonStringEntity( + new ResourceGroupRolesEntity(provider, tenantDomain, roleActions, resourceGroup))) + .build(); + execute(request, response -> readEntity(response, Void.class)); + } + + @Override + public Set<RoleAction> getTenantResourceGroups(AthenzDomain tenantDomain, AthenzIdentity provider, + String resourceGroup) { + URI uri = zmsUrl.resolve(String.format("domain/%s/service/%s/tenant/%s/resourceGroup/%s", + provider.getDomainName(), provider.getName(), tenantDomain.getName(), resourceGroup)); + HttpUriRequest request = RequestBuilder.get() + .setUri(uri) + .build(); + ResourceGroupRolesEntity result = execute(request, response -> readEntity(response, ResourceGroupRolesEntity.class)); + return result.roles.stream().map(rgr -> new RoleAction(rgr.role, rgr.action)).collect(Collectors.toSet()); + } + + @Override public void addRoleMember(AthenzRole role, AthenzIdentity member, Optional<String> reason) { URI uri = zmsUrl.resolve(String.format("domain/%s/role/%s/member/%s", role.domain().getName(), role.roleName(), member.getFullName())); MembershipEntity membership = new MembershipEntity.RoleMembershipEntity(member.getFullName(), true, role.roleName(), null); diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java index 7dd0585bfd4..b1c26923113 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/ZmsClient.java @@ -34,6 +34,12 @@ public interface ZmsClient extends AutoCloseable { void deleteProviderResourceGroup(AthenzDomain tenantDomain, AthenzIdentity providerService, String resourceGroup, OktaIdentityToken identityToken, OktaAccessToken accessToken); + /** For manual tenancy provisioning - only creates roles/policies on provider domain */ + void createTenantResourceGroup(AthenzDomain tenantDomain, AthenzIdentity provider, String resourceGroup, + Set<RoleAction> roleActions); + + Set<RoleAction> getTenantResourceGroups(AthenzDomain tenantDomain, AthenzIdentity provider, String resourceGroup); + void addRoleMember(AthenzRole role, AthenzIdentity member, Optional<String> reason); void deleteRoleMember(AthenzRole role, AthenzIdentity member); diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/ProviderResourceGroupRolesRequestEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/ResourceGroupRolesEntity.java index a67bd4dcad6..865dc8c02cb 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/ProviderResourceGroupRolesRequestEntity.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/bindings/ResourceGroupRolesEntity.java @@ -1,39 +1,53 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.athenz.client.zms.bindings; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzIdentity; -import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.client.zms.RoleAction; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; /** * @author bjorncs */ -public class ProviderResourceGroupRolesRequestEntity { +@JsonIgnoreProperties(ignoreUnknown = true) +public class ResourceGroupRolesEntity { @JsonProperty("domain") - private final String domain; + public final String domain; @JsonProperty("service") - private final String service; + public final String service; @JsonProperty("tenant") - private final String tenant; + public final String tenant; @JsonProperty("roles") - private final List<TenantRoleAction> roles; + public final List<TenantRoleAction> roles; @JsonProperty("resourceGroup") - private final String resourceGroup; + public final String resourceGroup; - public ProviderResourceGroupRolesRequestEntity(AthenzIdentity providerService, AthenzDomain tenantDomain, Set<RoleAction> rolesActions, String resourceGroup) { + @JsonCreator + public ResourceGroupRolesEntity(@JsonProperty("domain") String domain, + @JsonProperty("service") String service, + @JsonProperty("tenant") String tenant, + @JsonProperty("roles") List<TenantRoleAction> roles, + @JsonProperty("resourceGroup") String resourceGroup) { + this.domain = domain; + this.service = service; + this.tenant = tenant; + this.roles = roles; + this.resourceGroup = resourceGroup; + } + + public ResourceGroupRolesEntity(AthenzIdentity providerService, AthenzDomain tenantDomain, Set<RoleAction> rolesActions, String resourceGroup) { this.domain = providerService.getDomainName(); this.service = providerService.getName(); this.tenant = tenantDomain.getName(); @@ -43,10 +57,10 @@ public class ProviderResourceGroupRolesRequestEntity { public static class TenantRoleAction { @JsonProperty("role") - private final String role; + public final String role; @JsonProperty("action") - private final String action; + public final String action; public TenantRoleAction(String role, String action) { this.role = role; diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpRequestStrategy.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpRequestStrategy.java index 58112f86090..345177dcf36 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpRequestStrategy.java +++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/HttpRequestStrategy.java @@ -163,9 +163,9 @@ class HttpRequestStrategy implements RequestStrategy { return true; } - breaker.failure(response); logResponse(FINE, response, request, attempt); if (response.code() == 500 || response.code() == 502 || response.code() == 504) { // Hopefully temporary errors. + breaker.failure(response); return retry(request, attempt); } diff --git a/vespajlib/abi-spec.json b/vespajlib/abi-spec.json index e5d5b8ba5b6..e68a37b15b6 100644 --- a/vespajlib/abi-spec.json +++ b/vespajlib/abi-spec.json @@ -1203,6 +1203,7 @@ "public com.yahoo.tensor.Tensor notEqual(com.yahoo.tensor.Tensor)", "public com.yahoo.tensor.Tensor approxEqual(com.yahoo.tensor.Tensor)", "public com.yahoo.tensor.Tensor bit(com.yahoo.tensor.Tensor)", + "public com.yahoo.tensor.Tensor hamming(com.yahoo.tensor.Tensor)", "public com.yahoo.tensor.Tensor avg()", "public com.yahoo.tensor.Tensor avg(java.lang.String)", "public com.yahoo.tensor.Tensor avg(java.util.List)", @@ -2189,6 +2190,22 @@ ], "fields": [] }, + "com.yahoo.tensor.functions.ScalarFunctions$Hamming": { + "superClass": "java.lang.Object", + "interfaces": [ + "java.util.function.DoubleBinaryOperator" + ], + "attributes": [ + "public" + ], + "methods": [ + "public void <init>()", + "public static double hamming(double, double)", + "public double applyAsDouble(double, double)", + "public java.lang.String toString()" + ], + "fields": [] + }, "com.yahoo.tensor.functions.ScalarFunctions$LeakyRelu": { "superClass": "java.lang.Object", "interfaces": [ @@ -2557,6 +2574,7 @@ "public static java.util.function.DoubleBinaryOperator pow()", "public static java.util.function.DoubleBinaryOperator squareddifference()", "public static java.util.function.DoubleBinaryOperator subtract()", + "public static java.util.function.DoubleBinaryOperator hamming()", "public static java.util.function.DoubleUnaryOperator abs()", "public static java.util.function.DoubleUnaryOperator acos()", "public static java.util.function.DoubleUnaryOperator asin()", diff --git a/vespajlib/src/main/java/com/yahoo/collections/CopyOnWriteHashMap.java b/vespajlib/src/main/java/com/yahoo/collections/CopyOnWriteHashMap.java index 857b7cc6acd..98370a8735a 100644 --- a/vespajlib/src/main/java/com/yahoo/collections/CopyOnWriteHashMap.java +++ b/vespajlib/src/main/java/com/yahoo/collections/CopyOnWriteHashMap.java @@ -9,7 +9,7 @@ import java.util.Map; import java.util.Set; /** - * A hashmap wrapper which defers cloning of the enclosed map until it is written. + * A hashmap wrapper which defers cloning of the enclosed map until it is written to. * Use this to make clones cheap in maps which are often not further modified. * <p> * As with regular maps, this can only be used safely if the content of the map is immutable. diff --git a/vespajlib/src/main/java/com/yahoo/protect/Process.java b/vespajlib/src/main/java/com/yahoo/protect/Process.java index f3674f665b2..8038382c348 100644 --- a/vespajlib/src/main/java/com/yahoo/protect/Process.java +++ b/vespajlib/src/main/java/com/yahoo/protect/Process.java @@ -74,9 +74,13 @@ public final class Process { } } - public static void dumpHeap(String filePath, boolean live) throws IOException { + public static void dumpHeap(String filePath, boolean live) { log.log(Level.INFO, "Will dump the heap to '" + filePath + "', with the live = " + live); - getHotspotMXBean().dumpHeap(filePath, live); + try { + getHotspotMXBean().dumpHeap(filePath, live); + } catch (IOException e) { + log.log(Level.WARNING, "Failed writing heap dump:", e); + } } private static HotSpotDiagnosticMXBean getHotspotMXBean() throws IOException { diff --git a/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java b/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java index ab475e25387..3d4536d9249 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/Tensor.java @@ -36,6 +36,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import static com.yahoo.text.Ascii7BitMatcher.charsAndNumbers; +import static com.yahoo.tensor.functions.ScalarFunctions.Hamming; /** * A multidimensional array which can be used in computations. @@ -241,6 +242,7 @@ public interface Tensor { default Tensor notEqual(Tensor argument) { return join(argument, (a, b) -> ( a != b ? 1.0 : 0.0)); } default Tensor approxEqual(Tensor argument) { return join(argument, (a, b) -> ( approxEquals(a,b) ? 1.0 : 0.0)); } default Tensor bit(Tensor argument) { return join(argument, (a,b) -> ((int)b < 8 && (int)b >= 0 && ((int)a & (1 << (int)b)) != 0) ? 1.0 : 0.0); } + default Tensor hamming(Tensor argument) { return join(argument, (a,b) -> Hamming.hamming(a,b)); } default Tensor avg() { return avg(Collections.emptyList()); } default Tensor avg(String dimension) { return avg(Collections.singletonList(dimension)); } diff --git a/vespajlib/src/main/java/com/yahoo/tensor/functions/ScalarFunctions.java b/vespajlib/src/main/java/com/yahoo/tensor/functions/ScalarFunctions.java index 3ee9e67cdd6..d6fcd17b8fb 100644 --- a/vespajlib/src/main/java/com/yahoo/tensor/functions/ScalarFunctions.java +++ b/vespajlib/src/main/java/com/yahoo/tensor/functions/ScalarFunctions.java @@ -33,6 +33,7 @@ public class ScalarFunctions { public static DoubleBinaryOperator pow() { return new Pow(); } public static DoubleBinaryOperator squareddifference() { return new SquaredDifference(); } public static DoubleBinaryOperator subtract() { return new Subtract(); } + public static DoubleBinaryOperator hamming() { return new Hamming(); } public static DoubleUnaryOperator abs() { return new Abs(); } public static DoubleUnaryOperator acos() { return new Acos(); } @@ -152,6 +153,26 @@ public class ScalarFunctions { public String toString() { return "f(a,b)(a - b)"; } } + + public static class Hamming implements DoubleBinaryOperator { + public static double hamming(double left, double right) { + double distance = 0; + byte a = (byte) left; + byte b = (byte) right; + for (int i = 0; i < 8; i++) { + byte bit = (byte) (1 << i); + if ((a & bit) != (b & bit)) { + distance += 1; + } + } + return distance; + } + @Override + public double applyAsDouble(double left, double right) { return hamming(left, right); } + @Override + public String toString() { return "f(a,b)(hamming(a,b))"; } + } + // Unary operators ------------------------------------------------------------------------------ |